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;
}
原文地址:https://www.cnblogs.com/suxxsfe/p/13335043.html