• Codeforces Round #656 (Div. 3)


    咋感觉比一般的div3难点/jk

    http://codeforces.com/contest/1385/problem/A
    http://codeforces.com/contest/1385/problem/B
    http://codeforces.com/contest/1385/problem/C
    http://codeforces.com/contest/1385/problem/D
    前四个简单说吧,A随便做做就行,我写的似乎有点麻烦,B是按照每个数第一次出现的顺序来确定排列
    C的话观察一下发现一个数列是“好的”要求存在一个 (i),使得 ([1,i]) 单调不降,([i,n]) 单调不升
    D是用一个 (work(l,r,c)) 来递归的解决 (s_lcdots s_r) 变成一个 c-good 的数组最少要多少步
    就用 (left) 表示把 ([1,mid]) 变成 (c) 所需步数(就是有几个字符不是 (c)),(right) 表示 ([mid+1,r]) 所需步数
    (work(l,r,c)=min(left+work(mid+1,r,c+1),right+work(l,mid,c+1)))
    (l=r) 时开始回溯,复杂度 (O(nlog n))

    A

    int main(){int T=read();while(T--){
    	int x=read(),y=read(),z=read();
    	if(x==y&&z<=x) printf("YES
    %d %d %d
    ",x,z,1);
    	else if(x==z&&y<=x) printf("YES
    %d %d %d
    ",y,x,1);
    	else if(y==z&&x<=y) printf("YES
    %d %d %d
    ",x,1,y);
    	else puts("NO");
    }
    	return 0;
    }
    

    B

    int t[55];
    int main(){int T=read();while(T--){
    	int n=read();
    	for(reg int x,i=1;i<=n*2;i++){
    		x=read();
    		if(!t[x]){
    			t[x]=1;
    			printf("%d ",x);
    		}
    	}
    	std::memset(t,0,sizeof t);
    	EN;
    }
    	return 0;
    }
    

    C

    int a[200005];
    int main(){int T=read();while(T--){
    	int n=read();
    	for(reg int i=1;i<=n;i++) a[i]=read();
    	reg int i=n;
    	while(a[i]<=a[i-1]&&i>1) i--;
    	while(a[i]>=a[i-1]&&i>1) i--;
    	printf("%d
    ",i-1);
    }
    	return 0;
    }
    

    D

    char s[200005];
    int work(int l,int r,char c){
    	if(l==r) return s[l]!=c;
    	int mid=(l+r)>>1;
    	int left=0,right=0;
    	for(reg int i=l;i<=mid;i++) left+=s[i]!=c;
    	for(reg int i=mid+1;i<=r;i++) right+=s[i]!=c;
    	return std::min(left+work(mid+1,r,c+1),right+work(l,mid,c+1));
    }
    int main(){int T=read();while(T--){
    	int n=read();scanf("%s",s+1);
    	printf("%d
    ",work(1,n,'a'));
    }
    	return 0;
    }
    

    CF1385E Directing Edges

    http://codeforces.com/contest/1385/problem/E
    一个图,有一些边是有向的,有一些还没确定方向
    为这些没确定方向的边确定方向,能否使之不存在环。如果能,输出一种方案
    比赛时口胡了一个离谱做法,赛后才知道要按拓扑序连边

    首先建图的时候只把有向边建进去,判一下有没有环,有的话肯定是不存在
    没有的话按照拓扑序,从小到大连边,如果拓扑序相同,则按编号从小到大连边(这是为了防止一堆无向边连成环)
    这样,从每个点出发,都只会到达比它拓扑序或编号更大的点,自然就不会回到自身了,也就没有了环

    果然这种图中同时有向、无向边的时候,可能还是需要把两种边分开考虑

    #define N 200005
    #define M 200005
    struct graph{
    	int fir[N],nex[M],to[M],tot,t[M];
    	inline void add(int u,int v){
    		to[++tot]=v;
    		nex[tot]=fir[u];fir[u]=tot;
    	}
    }G;
    int n,m;
    int q[N],tail,head,in[N];
    int in_deg[N],f[N];
    int x[N],y[N];
    int vis[N],instack[N];
    int dfs(int u){
    	vis[u]=instack[u]=1;
    	for(reg int i=G.fir[u],v;i;i=G.nex[i]){
    		v=G.to[i];
    		if(instack[v]) return 0;
    		if(!vis[v]&&!dfs(v)) return 0;
    	}
    	instack[u]=0;
    	return 1;
    }
    inline void topo(){
    	head=-1;tail=0;
    	for(reg int i=1;i<=n;i++)if(!in_deg[i]) q[++head]=i;
    	reg int u,v;
    	while(tail<=head){
    		u=q[tail++];
    		for(reg int i=G.fir[u];i;i=G.nex[i]){
    			v=G.to[i];
    			f[v]=std::max(f[v],f[u]+1);
    			if(!--in_deg[v]) q[++head]=v;
    		}
    	}
    }
    int main(){int T=read();
    while(T--){
    	n=read();m=read();
    	for(reg int t,u,v,i=1;i<=m;i++){
    		t=read();u=read();v=read();
    		if(t) G.add(u,v),in_deg[v]++;
    		else x[++x[0]]=u,y[x[0]]=v;
    	}
    	int flag=1;
    	for(reg int i=1;i<=n;i++)if(!vis[i]&&!dfs(i)){flag=0;break;}
    	if(!flag) puts("NO");
    	else{
    		topo();
    		puts("YES");
    //		printf("%d %d
    ",n,m);
    		for(reg int i=1;i<=n;i++)
    			for(reg int j=G.fir[i];j;j=G.nex[j]) printf("%d %d
    ",i,G.to[j]);
    		for(reg int i=1;i<=x[0];i++)
    			if(f[x[i]]<f[y[i]]||(f[x[i]]==f[y[i]]&&x[i]<y[i])) printf("%d %d
    ",x[i],y[i]);
    			else printf("%d %d
    ",y[i],x[i]);
    	}
    	for(reg int i=1;i<=n;i++) G.fir[i]=vis[i]=instack[i]=in_deg[i]=0;
    	G.tot=0;x[0]=0;
    }
    	return 0;
    }
    

    CF1385F Removing Leaves

    http://codeforces.com/contest/1385/problem/F
    给一棵树,每次操作,选定一个节点,删除它相邻的 (k) 个叶子节点,直到不存在这样可以选的节点,问最多操作几次

    维护一个 (leaves_i) 表示 (i) 相邻的有几个叶子节点,在用一个STL的 set(set_i) 存它相邻的不是叶子的节点
    把这些 (leaves) 用优先队列维护起来,每次取出一个最大的,设为 (u)
    只要 (leaves_uge k),就不断让 (leaves_u,deg_u) 减去 (k),答案加一,其中 (deg) 表示一个点的度数

    如果这一通改完以后,如果 (deg_u=1),说明 (u) 变成了叶子节点,并且它唯一相邻的这个节点不是叶子节点(也就是 (leaves_u=0)),此时就要用到 (set_u)
    (set_u) 找出它相邻的这个点 (v),因为 (u) 以前不是叶子节点,但现在是了,所以在 (set_v) 中抹去 (u)(leaves_v) 也要加一
    如果 (leaves_vge k) 了,就把 (v) 加入队列(原来队列里的 (v) 此时就废弃了,所以每次取出的时候要判断一下)
    这样做的原因也就是因为每次选取一个点进行操作后,可能会为这个点相邻的非叶子节点带来新的叶子,所以要反复加入回队列进行更新(比如说样例三)

    但发现这样并不能很好的应对 (k=1) 的情况,所以特判一下,输出 (n-1) 就行了

    #define N 200005
    #define M 400005
    struct graph{
    	int fir[N],nex[M],to[M],tot,t[M];
    	inline void add(int u,int v){
    		to[++tot]=v;
    		nex[tot]=fir[u];fir[u]=tot;
    	}
    }G;
    struct data{
    	int id,leaves;
    };
    inline int operator < (const data &a,const data &b){
    	return a.leaves<b.leaves;
    }
    int n;
    std::set<int>set[N];//存相邻的度数不为 1 的点
    std::priority_queue<data>q;
    int leaves[N],deg[N]; 
    void dfs(int u,int fa){
    	if(deg[fa]==1) leaves[u]=1,set[u].erase(fa);
    	for(reg int v,i=G.fir[u];i;i=G.nex[i]){
    		v=G.to[i];
    		if(v==fa) continue;
    		if(deg[v]==1) set[u].erase(v),leaves[u]++;
    		dfs(v,u);
    	}
    }
    int main(){int T=read();while(T--){
    	n=read();int k=read();
    	for(reg int u,v,i=1;i<n;i++){
    		u=read();v=read();
    		G.add(u,v);G.add(v,u);
    		deg[v]++;deg[u]++;
    		set[u].insert(v);set[v].insert(u);
    	}
    	if(k==1){
    		printf("			%d
    ",n-1);
    		for(reg int i=1;i<=n;i++) set[i].clear(),G.fir[i]=leaves[i]=deg[i]=0;
    		G.tot=0;
    		continue;
    	} 
    	dfs(1,0);
    	for(reg int i=1;i<=n;i++) q.push((data){i,leaves[i]});
    	reg int u,v;int ans=0;
    	while(!q.empty()){
    		if(leaves[q.top().id]!=q.top().leaves){//判断一下 u 是否有效
    			q.pop();continue;
    		}
    		u=q.top().id;q.pop();
    		if(leaves[u]<k) break;
    		while(leaves[u]>=k) leaves[u]-=k,deg[u]-=k,ans++;
    		if(!leaves[u]&&deg[u]==1){ 
    			v=*set[u].begin();
    			leaves[v]++;
    			set[v].erase(u);
    			if(leaves[v]>=k) q.push((data){v,leaves[v]});
    		}
    	}
    	for(reg int i=1;i<=n;i++) set[i].clear(),G.fir[i]=leaves[i]=deg[i]=0;
    	while(!q.empty()) q.pop();G.tot=0;
    	printf("			%d
    ",ans);
    }
    	return 0;
    }
    

    CF1385G Columns Swaps

    https://codeforces.com/problemset/problem/1385/G
    给出两行数,每行 (n) 个,都小于等于 (n)
    每次操作,交换同一列的两个数。问能不能通过若干操作使得两行数每一行都是长度为 (n) 的排列,如果能,求最小操作次数

    首先如果有某个数出现次数不是 (2),显然不能
    看了眼标签发现有 2-sat,不过就算不看也很容易想到 (1ldots n) 表示不交换,(n+1ldots 2n) 表示交换相应的列
    然后写着写着发现这样建图出来的都是双向边,所以直接并查集就行了

    考虑当前是第 (i) 行,第一行和第二行的数分别是 (a_i,b_i)

    1. 不交换
      • 如果第一行有两个 (a_i),那么这一列不交换,另一个 (a_i) 所在的那一列必须交换(因为要保证每一行都只有一个 (a_i)
      • 如果只有一个 (a_i),这一行不交换,第二行中的那个 (a_i) 所在的列也不交换
      • 对于第二行的数(也就是 (b_i)),上面这两种情况也是同理
    2. 交换
      • 如果第一行有两个 (a_i),这一列交换,则另一个必须不换
      • 如果只有一个,这一行不换,那么第二行的那个 (a_i) 所在列也必须交换
      • (b_i) 同理

    根据上面的描述合并集合即可,具体实现上,用数组 _1[num][0/1],_2[num][0/1] 来表示第 (1,2) 行的第 (1,2)(num) 在哪一列(如果没有就是 (0),所以也可以用这个数组来判断一行有几个 (num)

    然后合并完以后,一个集合内的所有元素要么都成立,要么都不成立
    所以,如果 (i,i+n) 在同一集合,则无解

    然后对于每个集合,在合并的时候记录一个 (val),表示这个集合有多少个 (>n) 的元素(如果让这个集合成立,需要几次操作)
    每次在 (i,i+n) 中贪心地选 (val) 小的让它成立,打上标记就行了

    #define N 400005
    int n;
    int _1[N][2],_2[N][2];
    int a[N],b[N];
    int t[N];
    int fa[N],val[N],vis[N],Ans[N];
    int find(int k){return fa[k]==k?k:fa[k]=find(fa[k]);}
    inline void link(int x,int y){
    	x=find(x);y=find(y);
    	if(x!=y) fa[x]=y,val[y]+=val[x];
    }
    int main(){int T=read();while(T--){
    	n=read();
    	for(reg int i=1;i<=n;i++)
    		vis[i]=vis[i+n]=t[i]=_1[i][0]=_1[i][1]=_2[i][0]=_2[i][1]=0,
    		fa[i]=i,fa[i+n]=i+n,val[i]=0,val[i+n]=1;
    	for(reg int x,i=1;i<=n;i++){
    		a[i]=x=read();t[x]++;
    		if(_1[x][0]) _1[x][1]=i;
    		else _1[x][0]=i;
    	}
    	for(reg int x,i=1;i<=n;i++){
    		b[i]=x=read();t[x]++;
    		if(_2[x][0]) _2[x][1]=i;
    		else _2[x][0]=i;
    	}
    	for(reg int i=1;i<=n;i++)if(t[i]!=2){
    		puts("-1");	goto FAIL;
    	}
    	for(reg int i=1;i<=n;i++){
    		if(a[i]==b[i]) continue;
    		//不换
    		if(_1[a[i]][1])	link(i,_1[a[i]][i==_1[a[i]][0]]+n);//另一个 a[i] 必须换
    		else link(i,_2[a[i]][0]);//另一个 a[i](在第二行)必须不换
    		if(_2[b[i]][1]) link(i,_2[b[i]][i==_2[b[i]][0]]+n);
    		else if(_2[b[i]][0]) link(i,_1[b[i]][0]);
    		//换 
    		if(_1[a[i]][1]) link(i+n,_1[a[i]][i==_1[a[i]][0]]);//另一个必须不换
    		else link(i+n,_2[a[i]][0]+n);//这行只有一个 a[i],换走了,另一个(在第二行)必须换 
    		if(_2[b[i]][1]) link(i+n,_2[b[i]][i==_2[b[i]][0]]);
    		else link(i+n,_1[b[i]][0]+n);
    	}
    	for(reg int i=1;i<=n;i++)if(find(i)==find(i+n)){
    		puts("-1");goto FAIL;
    	}
    //	puts("YES");
    	for(reg int i=1,fi,fin;i<=n;i++)if(!vis[fi=find(i)]&&!vis[fin=find(i+n)]){
    		if(val[fi]<=val[fin]) vis[fi]=1;
    		else vis[fin]=1;
    	}
    	Ans[0]=0;
    	for(reg int i=1;i<=n;i++)if(vis[find(i+n)]) Ans[++Ans[0]]=i;
    	printf("%d
    ",Ans[0]);
    	for(reg int i=1;i<=Ans[0];i++) printf("%d ",Ans[i]);
    	EN;
    	FAIL:;
    }
    	return 0;
    }
    
  • 相关阅读:
    linux中实现将多个连续的字符拆分为单个字符
    linux 中删除当前目录下指定文件外所有的文件
    linux中将指定行数据合并为一行数据
    gz文件压缩、解压缩保留源文件
    R语言中提取多个连续值的累计的中间位点
    .net的委托和事件的直接理解
    消息是什么
    显示进度条的无组件上传!!
    与Java相关的四十个名字
    循环为Label赋值!
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/13335043.html
Copyright © 2020-2023  润新知