Codeforces Round #668 (Div. 1)

本文深入解析了五道算法竞赛题目,包括平衡字符串、树标签、固定点移除、对的游戏和砖块问题,涵盖字符串处理、图论、数据结构和网络流等关键概念。

正题

      A. Balanced Bitstring

      这样来考虑:当前区间k合法,那么要往右移一位依然合法,那么必须要保证加进来的位置=弹出去的位置,也就是说对于i<=n-k,要保证a[i]=a[i+k],我们判断一下输入的式子是否满足这样的性质就可以了,如果一个相等集合内只有问号,那么我们就待定它,最后看一下01个数是否都没有超过k/2即可.

#include<bits/stdc++.h>
using namespace std;

const int N=300010;
char s[N];
int T,n,k;

int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d %d",&n,&k);
		scanf("%s",s+1);
		bool tf=true;
		for(int i=1;i<=k;i++) {
			int op=0;
			for(int j=i;j<=n;j+=k)
				if(s[j]=='1') op|=1;
				else if(s[j]=='0') op|=2;
			if(op==3) {tf=false;break;};
			if(!op) continue;
			if(op==1) s[i]='1';
			else s[i]='0';
		}
		if(!tf) printf("NO\n");
		else{
			int t1=0,t2=0;
			for(int i=1;i<=k;i++) 
				if(s[i]=='1') t1++;
				else if(s[i]=='0') t2++;
			printf(t1<=k/2 && t2<=k/2?"YES\n":"NO\n");
		}
	}
}

      B. Tree Tag     

      比赛的时候想的并不是很清楚,以Alice为根建树,首先如果da*2>=db,那么肯定是Alice赢,因为它可以走到一个刚好距离Bob db的位置,然后Bob往外跳一定是输.考虑da*2<db,如果第一步Alice就可以抓到Bob,输出Alice,否则我们看直径是否>2*da,如果没有这样的直径,那么输出Alice,因为只要Alice站在直径的中心点,下一步肯定可以抓到Bob.如果有这样的直径,而且Alice第一步抓不到Bob,那么我们先让Bob往子树里最深的点跳,知道跳不动,如果Alice下一步可以抓到Bob,那么Bob就往外跳,可以分类讨论Bob初始点是否为直径端点,来证明Bob一定可以跳到一个Alice抓不到的地方,然后让bob反复横跳就可以了.

#include<bits/stdc++.h>
using namespace std;

const int N=100010;
struct edge{
	int y,nex;
}s[N<<1];
int first[N],len=0;
int T,n,a,b,da,db,op;
int mx,p,q;

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
}

void dfs(int x,int fa,int dep){
	if(dep>mx) mx=dep,p=x;
	if(x==b) op=dep;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa) dfs(s[i].y,x,dep+1);
}

void ga(int x,int fa,int dep){
	if(dep>mx) mx=dep,q=x;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa) ga(s[i].y,x,dep+1);
}

int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d %d %d %d %d",&n,&a,&b,&da,&db);
		for(int i=1;i<=n;i++) first[i]=0;len=0;
		int x,y;mx=0;
		for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
		if(2*da>=db){printf("Alice\n");continue;}
		dfs(a,0,0);
		if(op<=da){printf("Alice\n");continue;}
		mx=0;ga(p,0,0);
		printf(mx>2*da?"Bob\n":"Alice\n");
	}
}

      C. Fixed Point Removal

      考虑一次怎么做,d[i]表示[1,i]被删除的个数,如果i<a[i],那么这个位置必没有贡献d[i]=d[i-1],否则d[i]=d[i-1]+(i-a[i]>=d[i-1]).可以发现,考虑y=0的情况,我们发现对于i,可以使i产生贡献的是一个前缀,那么我们要找到这个前缀最右是多少,定义ans[i]为当x<=ans[i]时i可以产生贡献,令b[i]=i-a[i],那么当b[i]=0时,有ans[i]=i,否则相当于在前面的ans[i]中二分一个mid,如果ans[i]>=mid的个数>=b[i],那么mid就是可行的ans,将二分放到线段树上即可.

      发现询问只要满足x<=i<=y,x<=ans[i]<=y,显然用主席树维护即可.

#include<bits/stdc++.h>
using namespace std;

const int N=300010;
int n,q;
int a[N],rt[N],ans[N];
vector<int> V[N];
int t[N<<2],tot[6000010],ls[6000010],rs[6000010],T;

int ga(int now,int x,int l=1,int r=n){
	if(l==r) return x<=t[now]?l:0;
	int mid=(l+r)/2;
	if(x<=t[now<<1|1]) return ga(now<<1|1,x,mid+1,r);
	else return ga(now<<1,x-t[now<<1|1],l,mid);
}

void ins(int now,int x,int l=1,int r=n){
	t[now]++;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) ins(now<<1,x,l,mid);
	else ins(now<<1|1,x,mid+1,r);
}

void insert(int&now,int las,int x,int l=1,int r=n){
	if(now==0) now=++T;
	tot[now]=tot[las]+1;
	if(l==r) {tot[now]=tot[las]+1;return ;}
	int mid=(l+r)/2;
	if(x<=mid) rs[now]=rs[las],insert(ls[now],ls[las],x,l,mid);
	else ls[now]=ls[las],insert(rs[now],rs[las],x,mid+1,r);
}

int get_sum(int now,int x,int y,int l=1,int r=n){
	if(now==0) return 0;
	if(x==l && y==r) return tot[now];
	int mid=(l+r)/2;
	if(y<=mid) return get_sum(ls[now],x,y,l,mid);
	else if(mid<x) return get_sum(rs[now],x,y,mid+1,r);
	else return get_sum(ls[now],x,mid,l,mid)+get_sum(rs[now],mid+1,y,mid+1,r);
}

int main(){
	scanf("%d %d",&n,&q);
	int x,y;
	for(int i=1;i<=n;i++) {
		scanf("%d",&x);
		if(i-x<0) ans[i]=1e9;
		else {
			ans[i]=min(ga(1,i-x),i);
			if(ans[i]) ins(1,ans[i]);
			else ans[i]=1e9;
		}
	}
	for(int i=1;i<=n;i++) if(ans[i]!=1e9) insert(rt[i],rt[i-1],ans[i]);
	else rt[i]=rt[i-1];
	while(q--){
		scanf("%d %d",&x,&y);y=n-y;
		printf("%d\n",get_sum(rt[y],x+1,y)-get_sum(rt[x],x+1,y));
	}
}	

      D. Game of Pairs

      首先n为偶数很simple.前n个中间划一刀,后n个中间划一刀,两两连边就可以,可以证明无论怎么取%n!=0

      其次是n为奇数,首先要证明一个东西,如果选出一些数mod 2n=n,那么可以转化为mod 2n=0,因为总和mod2n = n,所以每个pair选另外一个就可以满足.

      我们考虑构造一种方案使得%n下选出的是0到n-1,一个pair两个中只能选一个,%n同余的两个数只能选一个,那么就有2n个点2n条边,且每个点度数为2,两条边的种类不同,所以必定形成了多个简单偶数环,在这些偶数环中,我们只要隔一个选一个出来就可以

#include<bits/stdc++.h>
using namespace std;

const int N=1000010;
int n;
int b[N];
pair<int,int> a[N];
struct edge{
	int y,nex;
}s[N<<1];
int first[N],len=0,ans[N];
long long tot=0;
bool vis[N];

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
}

void dfs(int x,int fa,int dep){
	if(vis[x]) return ;
	vis[x]=true;if(dep&1) ans[++ans[0]]=x,tot+=x;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa)
		dfs(s[i].y,x,dep+1);
}

int main(){
	scanf("%d",&n);
	int k=0,l=1;
	while(n%2==0) n/=2,l*=2,k++;
	if(k>0){
		printf("First\n");fflush(stdout);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=l;j++) printf("%d ",(i-1)*l+j);
			for(int j=1;j<=l;j++) printf("%d ",(i-1)*l+j); 
		}
	}
	else{
		printf("Second\n");fflush(stdout);
		for(int i=1;i<=2*n;i++){
			scanf("%d",&b[i]);
			if(!a[b[i]].first) a[b[i]].first=i;
			else a[b[i]].second=i;
		}
		for(int i=1;i<=n;i++) 
			ins(a[i].first,a[i].second),ins(a[i].second,a[i].first);
		for(int i=1;i<=n;i++) ins(i,i+n),ins(i+n,i);
		for(int i=1;i<=2*n;i++) if(!vis[i]) dfs(i,0,0);
		if(tot%(2*n)==0) for(int i=1;i<=n;i++) printf("%d ",ans[i]);
		else for(int i=1;i<=n;i++){
			if(ans[i]==a[b[ans[i]]].first) printf("%d ",a[b[ans[i]]].second);
			else printf("%d ",a[b[ans[i]]].first);
		}
	}
	fflush(stdout);
	scanf("%d",&n);
}

      E. Bricks

      赛后看到就识别原题了.

      直接考虑网络流,S连到i表示割掉这条边 i选横着,i连到T表示割掉这条边 i选竖着,流量为o,o是一个足够大的数,对于两个横着相邻的点,我们先加上他们的贡献(也就是-1),然后在网络流里面新建一个点,将两个点连向它流量为inf,这个点连向汇点,表示如果有一个点选了竖着,那么这两点的贡献就不算数,要减去他们的贡献,也就是加上1,最后答案就是dinic()-点数*o+贡献.

      实验证明不连o也是可以的,因为连o主要是为了保证两边不会同时被割,但是一个点要么属于S集要么属于T集,两边不可能同时被割,然后这个本质上就是其他题解的最大独立集?

#include<bits/stdc++.h>
using namespace std;

const int N=120010;
int n,m;
struct edge{
	int y,nex,c;
}s[N*8];
char ch[210][210];
bool tf[210][210];
int first[N],head[N],len=1,tot,bg,ed,op,d[N],o;
int fx[4]={-1,1,0,0};
int fy[4]={0,0,-1,1};
queue<int> q;

void ins(int x,int y,int c){
	s[++len]=(edge){y,first[x],c};first[x]=len;
	s[++len]=(edge){x,first[y],0};first[y]=len;
}

int pos(int x,int y){
	return (x-1)*m+y;
}

bool bfs(){
	memset(d,0,sizeof(d));d[bg]=1;
	q.push(bg);
	for(int i=1;i<=ed;i++) first[i]=head[i];
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=first[x];i!=0;i=s[i].nex) if(!d[s[i].y] && s[i].c){
			d[s[i].y]=d[x]+1;
			q.push(s[i].y);
		}
	}
	return d[ed]!=0;
}

int dfs(int x,int t){
	if(x==ed) return t;
	int tot=0;
	for(int&i=first[x];i!=0;i=s[i].nex) if(s[i].c && d[s[i].y]==d[x]+1){
		int my=dfs(s[i].y,min(t-tot,s[i].c));
		s[i].c-=my;s[i^1].c+=my;tot+=my;
		if(t==tot) break;
	}
	return tot;
}

int dinic(){
	int dx=0,ans=0;
	while(bfs()){
		dx=dfs(bg,1e9);
		while(dx) ans+=dx,dx=dfs(bg,1e9);
	}
	return ans;
}

int main(){
	scanf("%d %d",&n,&m);o=3*n*m;bg=o+1;ed=o+2;
	for(int i=1;i<=n;i++) scanf("%s",ch[i]+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) if(ch[i][j]=='#'){
			tot++;
			if(ch[i][j+1]=='#'){
				tot--;
				ins(pos(i,j),n*m+pos(i,j),1e9);
				ins(pos(i,j+1),n*m+pos(i,j),1e9);
				ins(n*m+pos(i,j),ed,1);
			}
			if(ch[i+1][j]=='#'){
				tot--;
				ins(2*n*m+pos(i,j),pos(i,j),1e9);
				ins(2*n*m+pos(i,j),pos(i+1,j),1e9);
				ins(bg,2*n*m+pos(i,j),1);
			}
		}
	for(int i=1;i<=ed;i++) head[i]=first[i];
	printf("%d\n",dinic()+tot);
}

      总结

      这次比赛只做了前三题,因为第三题想到一半,后面都想错了,最后5分钟才发现可以直接看前面ans[i]来二分,所以后面两题看都没看,以后要吸取经验,想清楚点,t4赛后一个半小时莽出来了,t5也是赛后才看到题发现是原题,但网络流最小割的思想也有待复习.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值