牛客周赛85 DEF Java
牛客周赛85 DEF Java
- D小紫的优势博弈
- E小紫的线段染色
- F小紫的树上染色
D小紫的优势博弈
思路:枚举小红的所有情况,删除前1个,删除前2个 … 全部删除。这样得到了小紫的所有情况,然后从删除位置的后一个往后面数出现的‘0’和‘1’的个数,如果全是偶数就小紫赢。
这题我的思路是没问题:但是用了substring(),它的时间复杂度是O(k),这样一来总的时间复杂度是O(N*N) N=1e6,超出时间限制。其实没不必要用substring,内层循环直接这么写:for(int j=i;j<n;j++),一回事,这样时间复杂度降低了一点,也就通过了。
下面是超时代码:
修改后代码:
import java.util.*;
public class Main {
public static void main(String[]args){
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();//表示长度
String s=scan.next();
int cnt=0;
int cnt0=0;
int cnt1=0;
for(int i=1;i<n;i++){
cnt0=0;
cnt1=0;
for(int j=i;j<n;j++){
if(s.charAt(j)=='0'){
cnt0++;
}else{
cnt1++;
}
if(cnt0%2==0&&cnt1%2==0){
cnt++;
break;
}
}
}
double res=cnt*1.0/n;
System.out.print(res);
}
}
E小紫的线段染色
思路:
1.创建一个集合收集所有线段,该集合的元素类型是int类型的数组{l,r,次序}。
2.对所有线段排序,如果左边界不相同就按照左边界从小到大排序,左边界同,就按照右边界从小到大排序。
3.创建两个变量来记录此时最后红色线段的尾位置hongEnd和最后紫线段的尾位置ziEnd。
4.遍历所有线段,更新hongEnd和ziEnd,把涂成紫色的线段需要放入结果集合res中,最后看结果集合就可以了。
注意:优先选择ArrayList,因为LinkedList的get时间复杂度O(K)。
import java.util.*;
public class Main {
public static void main(String[]args){
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();//表示线段的数量
//创建一个集合,元素类型是int型的数组,{l,r,次序},不能用LinkedList 因为get的时间复杂度尾O(K)
ArrayList<Integer[]> list=new ArrayList();
for(int i=0;i<n;i++){
int l=scan.nextInt();
int r=scan.nextInt();
list.add(new Integer[]{l,r,i+1});
}
//对排序:如果两条线段的左边界相同,就按照右边界从小到大排序,如果左边界不同你就按照左边界从小到大排序
Collections.sort(list,(o1,o2)->{//O(N*logN)
if(o1[0].equals(o2[0])){
return Integer.compare(o1[1],o2[1]);
}else{
return Integer.compare(o1[0],o2[0]);
}
});
//创建存放结果的集合
ArrayList<Integer> res=new ArrayList();
//记录最后红色线段的尾位置
int hongEnd=list.get(0)[1];
//记录最后子线段的尾位置
int ziEnd=-1;
//遍历所有线段
for(int i=1;i<n;i++){
int curStart=list.get(i)[0];
int curEnd=list.get(i)[1];
//当前的线段是红色
//有两种情况:1.当前的lefe<=hongEnd,就必须染成紫色。确定了要染成紫色,需要看看ziEnd是否<left,如果>left,就紫色相交了
//2.当前的left大于hongend,就保持红色就好
if(curStart<=hongEnd){
//染成紫色
if(curStart>ziEnd){
//可以染
res.add(list.get(i)[2]);
ziEnd=curEnd;
}else{
//紫色相交了
System.out.print(-1);
return;
}
}else{
//保持红色
hongEnd=curEnd;
}
}
if(res.size()==0) {
res.add(1);
}
System.out.println(res.size());
for(Integer i:res) {
System.out.print(i+" ");
}
}
}
F小紫的树上染色
思路:题目求最大红色联通块的最小值,像这种用二分,最大红色块最小为0,最大为节点个数n,所以l=0,r=n。check(mid,k)定义为:在染紫次数<=k时,能使得最大红色联通块的个数为mid就返回true。check(mid,k)的具体操作:遍历这个无向图,从叶子结点开始(贪心思想),当该节点联通数量>mid就染紫色,记录一共染了多少个紫色,如果<=k就返回true。
步骤:
1.创建图,visit数组(遍历无向图用到),s数组(s[i]:与i节点联通的红色节点个数,加上自己)。
2.创建两个变量 l,r,开始二分查找。
import java.util.*;
public class Main {
public static void main(String[]args){
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();//节点个数
int k=scan.nextInt();//最多染成紫色的个数
visit=new boolean[n+1];
//创建图
graph=new ArrayList[n+1];
for(int i=0;i<=n;i++) {
graph[i]=new ArrayList<>();
}
for(int i=1;i<n;i++){
int a=scan.nextInt();
int b=scan.nextInt();
graph[a].add(b);
graph[b].add(a);
}
s=new int[n+1];
int l=0,r=n;
while(l<r){
int mid=(l+r)>>1;
need=0;
Arrays.fill(visit, false);
Arrays.fill(s, 0);
if(check(mid,k)){//在<=k的机会内,使得最大红色联通块为mid
r=mid;
}else{
l=mid+1;
}
}
System.out.println(l);
}
static ArrayList<Integer>[] graph;
static int need;//需要的紫色个数
static int[]s;//s[i]:与以i节点联通的红色节点个数加上自己
//定义: 在<=k的机会之内,是否能让最大红色联通块大小为mid
static boolean check(int mid,int k){
//从节点1开始遍历图
dfs(1,mid,k);
return need<=k;
}
static boolean visit[];//避免走回头路
static void dfs(int root,int mid,int k) {
visit[root]=true;
for(int neibor:graph[root]) {
if(visit[neibor]) {
continue;
}
dfs(neibor,mid,k);
//后序位置
s[root]+=s[neibor];
}
//加上自己
s[root]++;
//判断红色联通块的数量是否大于mid
if(s[root]>mid) {
//染紫色
need++;
s[root]=0;
}
}
}