Codeforces Round #668 (Div. 1) 题解

A

容易发现一个结论:每隔 k k k 位的字符应该是相同的。于是就可以确定这个字符串的一部分。

剩下的部分只需要保证 字符串前 k k k 个字符中 01 01 01 数量相同即可,剩下的 ? 也就确定了,代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 300010

int T,n,k;
char s[maxn];

int main()
{
    
    
	scanf("%d",&T);while(T--)
	{
    
    
		scanf("%d %d",&n,&k);
		scanf("%s",s+1);
		int ans=1,tot=0;
		for(int i=1;i<=k;i++){
    
    
			int p=-1;
			for(int j=i;j<=n;j+=k){
    
    
				if(s[j]!='?'){
    
    
					if(p!=-1&&p!=s[j]-'0')ans=0;
					else p=s[j]-'0';
				}
			}
			if(p!=-1){
    
    
				for(int j=i;j<=n;j+=k)
				if(s[j]=='?')s[j]=p+'0';
			}
		}
		if(ans){
    
    
			int zero=0,one=0;
			for(int i=1;i<=k;i++)if(s[i]=='0')zero++;else one++;
			for(int i=1;i<=k;i++)if(s[i]=='?'){
    
    
				if(zero<k/2)s[i]='0',zero++;
				else s[i]='1',one++;
			}
			zero=0,one=0;
			for(int i=1;i<=k;i++)if(s[i]=='0')zero++;else one++;
			if(zero!=one)ans=0;
			for(int i=k+1;i<=n;i++){
    
    
				if(s[i]=='?')s[i]=s[i-k];
				if(s[i]=='0')zero++;else one++;
				if(s[i-k]=='0')zero--;else one--;
				if(zero!=one)ans=0;
			}
		}
		if(ans)printf("YES\n");else printf("NO\n");
	}
}

B

想象一下一开始两人会怎么走:Alice肯定往Bob那里走,Bob肯定会远离Alice,最后Bob会被赶到叶子节点,然后Alice与他的距离一定是 d a da da,因为此时Alice就可以封锁Bob尽可能多的逃跑路线。

此时,Bob只有满足 d b > 2 × d a db>2\times da db>2×da 才能逃出去,所以这就是判输赢的条件。

但是还需要满足树上存在一条长度大于 2 × d a 2\times da 2×da 的链,不然Bob没地方跑,这个判一下直径长度即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010
#define pb push_back

int T,n,a,b,da,db;
vector<int>e[maxn];
int dis[maxn],fur;
void dfs(int x,int fa){
    
    
	if(dis[x]>dis[fur])fur=x;
	for(int y:e[x])if(y!=fa){
    
    
		dis[y]=dis[x]+1;
		dfs(y,x);
	}
}

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++)e[i].clear();
		for(int i=1,x,y;i<n;i++)scanf("%d %d",&x,&y),e[x].pb(y),e[y].pb(x);
		dis[a]=0;fur=a;dfs(a,0);
		if(dis[b]<=da){
    
    printf("Alice\n");continue;}
		dis[fur]=0;dfs(fur,0);
		if(db>da*2&&dis[fur]>da*2)printf("Bob\n");
		else printf("Alice\n");
	}
}

C

一个显然且有用的转化:先令每个位置与下标做差,即令 b i = i − a i b_i=i-a_i bi=iai

那么拿了一个 0 0 0 之后,后面的 1 1 1 就能拿了,拿了一个 i i i 之后,后面的 i + 1 i+1 i+1 就都能拿了。

先考虑如何能拿最多:肯定是每次拿最右边的且能拿的位置。转化一下,一个位置 i i i 能拿当且仅当它的左边有至少 b i b_i bi 个能拿(赛时这一步想错了一点然后就没了,太菜了QAQ)。

对于每个 i i i,求出最大的 c i c_i ci 表示左边界至少到 c i c_i ci 位置, i i i 才能拿。然后将询问排序用树状数组维护一下 c c c 就可以了。

代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 300010

int n,m,a[maxn];
struct TREE{
    
    
	int tr[maxn];
	void add(int x){
    
    for(;x<=n;x+=(x&-x))tr[x]++;}
	int sum(int x){
    
    int re=0;for(;x;x-=(x&-x))re+=tr[x];return re;}
}tr;
struct par{
    
    int x,pos;};
vector<par>q[maxn];
int ans[maxn];

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),a[i]=i-a[i];
	for(int i=1,x,y;i<=m;i++)scanf("%d %d",&x,&y),q[n-y].push_back((par){
    
    1+x,i});
	for(int i=1;i<=n;i++){
    
    
		if(a[i]>=0){
    
    
			int l=1,r=i,p=-1;
			while(l<=r){
    
    
				int mid=l+r>>1;
				if(tr.sum(i)-tr.sum(mid-1)>=a[i])p=mid,l=mid+1;
				else r=mid-1;
			}
			if(p!=-1)tr.add(p);
		}
		for(par j:q[i]){
    
    
			ans[j.pos]=tr.sum(i)-tr.sum(j.x-1);
		}
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
}

D

打表发现,奇数时 Second \text{Second} Second 必胜,偶数时 First \text{First} First 必胜。

先说偶数的构造方法:令 i i i i + n i+n i+n 一组。先假定 Second \text{Second} Second 取每组中较小的,那么取完之后的和模 n n n 一定是 n 2 \dfrac n 2 2n,对于任意一组,假如不取小的取大的,那么总和的变化量都是 n n n,即总和模 n n n 依然是 n 2 \dfrac n 2 2n,也就是说,无论怎么取,模 n n n 都是 n 2 \dfrac n 2 2n,那么模 2 n 2n 2n 就不可能为 0 0 0 Second \text{Second} Second 必败。

然后是奇数时的选取策略:此时 1 1 1 ~ 2 n 2n 2n 的和模 2 n 2n 2n n n n,也就是说,如果能找到一组选取方案模 n n n 0 0 0,那么就一定有解。因为找到的这组选取方案模 2 n 2n 2n 要么为 0 0 0 要么为 n n n,假如是 0 0 0 那它就是解,假如是 n n n 那么每一组换成另一个,这一个方案模 2 n 2n 2n 一定为 0 0 0

考虑如何找到一组选取方案模 n n n 0 0 0,此时需要满足 n n n 的剩余系中每个数都被拿了一个,即 0 + 1 + 2 + . . . + n − 1 ≡ 0 ( m o d n ) 0+1+2+...+n-1\equiv 0\pmod n 0+1+2+...+n10(modn)

n n n i i i 的只有 i i i i + n i+n i+n 两个,两者必拿其中一个,而每一组内也是两者必拿其中一个,考虑建一张图,同组数之间连红边, i i i i + 1 i+1 i+1 连蓝边,像这样(从官方题解那偷来的qwq):
在这里插入图片描述
然后就会形成很多个环,每个点恰好连接一条红边一条蓝边,那么每个环一定都是偶环,然后就可以黑白染色,白点集和黑点集分别对应一个方案。

代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 1000010
#define pb push_back

int n,p[maxn],last[maxn];
vector<int>e[maxn];
bool v[maxn];
vector<int>c[2];int C[2];
void dfs(int x,int col){
    
    
	v[x]=true;
	c[col].pb(x);C[col]=(C[col]+x)%(2*n);
	for(int y:e[x])if(!v[y])dfs(y,col^1);
}

int main()
{
    
    
	scanf("%d",&n);
	if(n%2==0){
    
    
		printf("First\n");fflush(stdout);
		for(int i=1;i<=2*n;i++)printf("%d ",i<=n?i:i-n);fflush(stdout);
	}else{
    
    
		printf("Second\n");fflush(stdout);
		for(int i=1;i<=2*n;i++){
    
    
			scanf("%d",&p[i]);
			if(last[p[i]])e[i].pb(last[p[i]]),e[last[p[i]]].pb(i);
			else last[p[i]]=i;
			if(i<=n)e[i].pb(i+n),e[i+n].pb(i);
		}
		for(int i=1;i<=2*n;i++)if(!v[i])dfs(i,0);
		for(int i=0;i<2;i++)if(!C[i]){
    
    
			for(int j:c[i])printf("%d ",j);
		}
		fflush(stdout);
	}
}

E

将放砖块转化为删边,假如两个黑块之间的公共边被删了,说明两者在同一块。

由于每删一条边砖块数 − 1 -1 1,那么问题就变成了最多能删多少条边,由于没有 L L L 形的砖,所以需要保证每一个黑块处都不能删出 L L L 形,这个问题可以转化一下:将边化点,让两个冲突的边之间连边,问题就变成了求最大独立集,由于这是二分图,用网络流解决即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,m,S,T,ans=0;
char s[210][210];
struct edge{
    
    int y,z,next;}e[maxn*6<<1];
int first[maxn],len=1;
void buildroad(int x,int y,int z){
    
    e[++len]=(edge){
    
    y,z,first[x]};first[x]=len;}
void ins(int x,int y,int z=1){
    
    buildroad(x,y,z);buildroad(y,x,0);/*printf("ins : %d %d\n",x,y);*/}
int h[maxn],q[maxn],st,ed,cur[maxn];
bool bfs(){
    
    
	memset(h,0,(T+1)<<2);
	h[q[st=ed=1]=S]=1;
	while(st<=ed){
    
    
		int x=q[st++];cur[x]=first[x];
		for(int i=first[x];i;i=e[i].next)
		if(!h[e[i].y]&&e[i].z)h[q[++ed]=e[i].y]=h[x]+1;
	}
	return h[T];
}
int dfs(int x,int flow){
    
    
	if(x==T)return flow; int tt=0;
	for(int i=cur[x];i;i=e[i].next){
    
    
		int y=e[i].y;cur[x]=i;
		if(h[y]==h[x]+1&&e[i].z){
    
    
			int p=dfs(y,min(flow-tt,e[i].z));tt+=p;
			e[i].z-=p;e[i^1].z+=p;
		}
		if(tt==flow)break;
	}
	if(!tt)h[x]=0;
	return tt;
}

int main()
{
    
    
	scanf("%d %d",&n,&m);S=n*(m-1)+m*(n-1)+1;T=S+1;
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
//	printf("S : %d  T : %d\n",S,T);
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++)if(s[i][j]=='#'){
    
    
			int L=j>1&&s[i][j-1]=='#'?(i-1)*(m-1)+j-1:0;
			int R=j<m&&s[i][j+1]=='#'?(i-1)*(m-1)+j:0;
			int U=i>1&&s[i-1][j]=='#'?(i-2)*m+j+n*(m-1):0;
			int D=i<n&&s[i+1][j]=='#'?(i-1)*m+j+n*(m-1):0;
			if(L&&U)ins(L,U);
			if(L&&D)ins(L,D);
			if(R&&U)ins(R,U);
			if(R&&D)ins(R,D);
		}
	}
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++)if(s[i][j]=='#'){
    
    
			ans++;
			if(j<m&&s[i][j+1]=='#')ins(S,(i-1)*(m-1)+j),ans--;
			if(i<n&&s[i+1][j]=='#')ins((i-1)*m+j+n*(m-1),T),ans--;
		}
	}
	while(bfs())ans+=dfs(S,n*m);
	printf("%d",ans);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/108449475