AtCoder Grand Contest 016

链接

C. +/- Rectangle

构造题。显然我们需要负数越少越好,这样我们每 \((w,h)\) 放一个负数,令他等于 \(-k(wh-1)-1\),其余点等于 \(k\)。可以发现这样每 \((w,h)\) 都是符合条件的。

但是这样总和取决于 \(W\%w,H\%h\) 的值。可以发现只要这两个值不是 \(0\),随便取 \(k=2333\) 都一定可以。

那么对于 \(w|W,h|H\) 的情况,由于每个负数最多贡献 \(wh\) 次,所以最后结果一定为负,即不合法。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
	int n,m,w,h;
	scanf("%d%d%d%d",&n,&m,&w,&h);
	if(n%w==0 && m%h==0){puts("No");return 0;}
	puts("Yes"); 
	int b=2333,p=-b*(w*h-1)-1;
	for(int i=1;i<=n;i++,puts(""))
			for(int j=1;j<=m;j++) printf("%d ",i%w==0 && j%h==0?p:b);
	return 0;
}

D. XOR Replace

考虑我们新建的一个权值为所有异或值的点,那么一次操作本质上就是把这个特殊点和某个点的权值交换。

首先如果序列不同显然无解,否则考虑:如果一个点从 \(a_i\) 要到 \(b_i\),那么必然有一个时刻特殊点权值为 \(b_i\) 然后和它交换。

不妨连一条 \(b_i\)\(a_i\) 的有向边,一开始有一个标记在特殊点权值上,每次可以移动标记到相邻点,对应依次交换。目标是把所有边都走过。

由于这张图每个点一定有入度等于出度,故对于每个联通块需要走恰好边数次,不同联通块之间需要恰好 1 次。

所以最后答案就是边数+联通块数-特殊点是否是孤立点。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int a[N],b[N],pa[N],pb[N],f[N];
int find(int x){return x==f[x]?f[x]:(f[x]=find(f[x]));}
bool vis[N];
int main()
{
	int n,v=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),v^=a[i];a[n+1]=v;v=0;
	for(int i=1;i<=n;i++) scanf("%d",&b[i]),v^=b[i];b[n+1]=v;++n;
	for(int i=1;i<=n;i++) pa[i]=a[i],pb[i]=b[i];
	sort(pa+1,pa+n+1);sort(pb+1,pb+n+1);
	for(int i=1;i<=n;i++) if(pa[i]!=pb[i]){puts("-1");return 0;}
	int m=unique(pa+1,pa+n+1)-pa-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(pa+1,pa+m+1,a[i])-pa;
	for(int i=1;i<=n;i++) b[i]=lower_bound(pa+1,pa+m+1,b[i])-pa;
	int ans=0;
	for(int i=1;i<=n;i++) if(a[i]!=b[i] || i==n) f[a[i]]=a[i],f[b[i]]=b[i],ans+=i<n;
	for(int i=1;i<=n;i++)
		if(a[i]!=b[i]) f[find(a[i])]=find(b[i]);
	for(int i=1;i<=m;i++) ans+=f[i]==i;
	printf("%d\n",max(ans-1,0));
	return 0;
}

E. Poor Turkeys

思维好题。

首先考虑如果需要某个鸡活下来,那么需要那些鸡去替他被吃。

具体来说,我们从后往前处理。假设我们想要 \(i\) 活下来,而 \(j\)\(i\) 必须吃一个,那么 \(j\) 就必须替 \(i\) 被吃。换句话,在此之前 \(j\) 必须活着。

那么对于某一时刻 \(j\)\(k\) 必须吃一个,而 \(j\) 要活到后面才能使 \(i\) 活,那么 \(k\) 也必须在这里死,在此之前必须或者。

如果 \(j\)\(k\) 都必须活,那么不好意思 \(i\) 必须得死了。

可以发现这样一只鸡最多只能抗一次,所以两只鸡同时活着的充要条件是替他们被吃的鸡没有交集。

直接暴力处理即可。复杂度 \(O(nm+\frac{n^3}{\omega})\)

#include<iostream>
#include<cstdio>
#include<bitset>
#define N 410
#define M 100010
using namespace std;
bitset<N>d[N];//d_i[j] : to save i, if j have to be die
int x[M],y[M];
bool ban[N];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]);
	for(int i=1;i<=n;i++)
	{
		d[i].set(i);
		for(int j=m;!ban[i] && j>=1;j--)
			if(d[i].test(x[j]) && d[i].test(y[j])) ban[i]=true;
			else if(d[i].test(x[j])) d[i].set(y[j]);
			else if(d[i].test(y[j])) d[i].set(x[j]);
	}
	int res=0;
	for(int i=1;i<=n;i++) if(!ban[i])
			for(int j=i+1;j<=n;j++) if(!ban[j] && !(d[i]&d[j]).any())
			{
//				printf("%d & %d\n",i,j);
//				for(int k=1;k<=n;k++) printf("%d ",d[i].test(k));puts("");
//				for(int k=1;k<=n;k++) printf("%d ",d[j].test(k));puts("");
				res++;
			}
	printf("%d\n",res);
	return 0;
}

F. Games on DAG

神仙题。

首先观察到 \(n\) 很小,往 \(2^n,3^n\) 方向考虑。

考虑如果边固定时怎么做。可以直接计算每个点的 \(\text{SG}\) 函数。具体来说每个点的 \(\text{SG}\) 值是其连出点 \(\text{SG}\)\(\text{mex}\)。最后要求 \(\text{SG}(1)\neq\text{SG}(2)\)

主要到最后的条件。那么我们不妨算出 \(\text{SG}(1)=\text{SG}(2)\) 的方案再用 \(2^m\) 去减。

考虑 \(\text{mex}\) 的性质。我们不妨从小往大做,状压已经求得 \(\text{SG}\) 的点。我们并不需要具体知道其中 \(\text{SG}\) 的具体值。

\(f_S\) 表示 只考虑\(S\) 集合,\(SG(1)=SG(2)\) 的方案数,考虑如何转移。

我们枚举 \(S\) 一个子集 \(T\),钦定 \(T\) 中的元素 \(\text{SG}(x)>0\),非 \(T\) 的元素 \(\text{SG}(x)=0\)。这样 \(T\) 中的每个元素都需要非 \(T\) 元素连至少一条边,而非 \(T\) 集合可以向 \(T\) 集合连边,也可以不连,但是不能向非 \(T\) 集合连边。

考虑 \(1,2\) 显然需要同时属于 \(T\) 集合或是同时不属于 \(T\) 集合。如果属于 \(T\) 集合那么 \(f_S\leftarrow f_T\),因为可以发现将 \(T\) 集合的 \(\text{SG}\) 整体加 \(1\) 就可以得到 \(S\) 集合的 \(\text{SG}\)

分类讨论一下即可。预处理一个点与某个点集的边数,总复杂度 \(O(m3^n)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 16
#define M 210
#define mod 1000000007
using namespace std;
int rd[N][1<<N],_2[M];//the road from u to set {S}
int f[1<<N];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=_2[0]=1;i<=m;i++) _2[i]=2ll*_2[i-1]%mod;
	int S=1<<n;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);--u,--v;
		rd[u][1<<v]++;
	}
	for(int i=0;i<n;i++)
		for(int s=1;s<S;s++) rd[i][s]=rd[i][s-(s&(-s))]+rd[i][s&(-s)];
	for(int s=3;s<S;s++)
	if(s%4==3)
	{
		f[s]=1;
		for(int t=s&(s-1);t;t=(t-1)&s)//the set except 0
		if(t%4==3)//both s,t \geq 0
		{
			int res=1;
			for(int i=0;i<n;i++) if((s>>i)&1)
			{
				if((t>>i)&1) res=1ll*res*(_2[rd[i][s^t]]-1)%mod;
				else res=1ll*res*_2[rd[i][t]]%mod;
			}
			f[s]=(f[s]+1ll*res*f[t])%mod;
		}
		else if(t%4==0)//both s,t = 0
		{
			int res=1;
			for(int i=0;i<n;i++) if((s>>i)&1)
			{
				if((t>>i)&1) res=1ll*res*(_2[rd[i][s^t]]-1)%mod*_2[rd[i][t]]%mod;
				else res=1ll*res*_2[rd[i][t]]%mod;
			}
			f[s]=(f[s]+res)%mod;
		}
	}
	printf("%d",(_2[m]-f[S-1]+mod)%mod);
	return 0;
}
posted @ 2023-09-20 18:55  Flying2018  阅读(21)  评论(0)    收藏  举报