首页 > 技术文章 > AGC050F NAND Tree

jz-597 2021-07-10 10:17 原文

有棵树,一开始节点上有01的权值。

每次可以缩一条边,两个点点权合并为其\(NAND\)和(先取and再取not)。

统计最终能缩成一个权值为1的点的方案数。答案对2取模。

\(n\le 300\)(其实能做\(n\le 5000\),甚至也许可以\(O(n)\)?)


神仙题。

先考虑\(n\)为奇数的情况。

观察1:把操作按顺序两个两个分为一组。如果存在某组内操作的边不相邻,那么这个操作无贡献。

如果存在,建立双射:把第一个操作的边不相邻的组操作的两条边交换顺序。

两者答案相同,因为对\(2\)取模所以抵消。

观察2:一组中的操作\(x\to y\to z\)(即变成\(NAND(NAND(x,y),z)\)),可以视作\(x\)吃掉了后面两个点(即操作过后变成\(x\)

打出\(NAND(NAND(x,y),z)\)的真值表,发现结果不等于\(x\)的时候,\(x=z\)

于是这和\(z\to y\to x\)结果一样,因为对2取模所以抵消,因此假装结果变成了\(x\)也没有问题。

结合观察1和观察2,问题变成每次选择个\(x\to y\to z\),然后\(x\)吃掉后面两个点。

观察3:令最后一个留下的点为\(rt\)(结果是\(rt\)的权值)。每组操作一定要和\(rt\)有关。

如果第一次某组操作和\(rt\)无关,将这组顺序完全交换,得到结果完全一样。显然这也是个双射。

于是又抵消了。

于是做法是:枚举\(rt\),对除了\(rt\)的节点划分成若干组,每组为相邻的节点(就是匹配)。模拟剥叶子可知如果存在一组完美匹配,匹配唯一。然后将各匹配缩点,新树的合法遍历顺序(每个点遍历前要先遍历父亲)个数就是答案。

时间\(O(n^2)\)

现在考虑\(n\)为偶数。naive的做法是直接枚举第一次操作的边然后变成奇数的问题,时间\(O(n^3)\)反正能过原题。

观察4:第一次操作的边缩点之后一定作为\(rt\)统计答案。

如果第一次操作的边不作为\(rt\)。先给边缩点,从此时的\(rt\)开始做个上面奇数情况的算法,搞出了个唯一的匹配。(如果能找到得到话)

找到这个边缩点所在的匹配。匹配大概长成\([(x- y)- z]\)\([x- (y- z)]\),小括号表示边缩点。

发现将\([(x- y)- z]\)换成\([x- (y- z)]\)(或反之),结果相同,并形成了双射。

(就是说一开始缩的边为\(x-y\),根为\(rt\),变成一开始缩的边为\(y-z\),根为\(rt\)

形成双射:一开始缩\(x-y\)的情况已经形成了合法的匹配。因为两种方案中匹配唯一,并且这样变换过后,除了\(x,y,z\)之外匹配保持不变,所以一开始缩\(y-z\)也是合法的匹配。并且两种方案中\(x,y,z\)都是在同一个匹配里的,于是可以双射。

于是枚举第一个缩的边,将其作为\(rt\),后面类似于奇数的做法,就可以\(O(n^2)\)啦!

最后想想,枚举根,划分唯一匹配,算遍历数,是不是能换根啊……

也许能\(O(n)\)啦。


using namespace std;
#include <bits/stdc++.h>
const int N=305;
int n;
struct EDGE{
	int to;
	EDGE *las;
};
struct Graph{
	EDGE e[N*2];
	int ne;
	EDGE *last[N];
	void link(int u,int v){
		e[ne]={v,last[u]};
		last[u]=e+ne++;
	}
	void clear(){
		ne=0;
		memset(last,0,sizeof(EDGE*)*(n+1));
	}
} G,F;
int c[N];
int C[N][N];
int sz[N],cov[N];
int predfs(int x,int fa){
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (ei->to!=fa){
			int t=predfs(ei->to,x);
			if (!t)
				return 0;
		}
	if (!cov[x]){
		if (cov[fa])
			return 0;
		cov[x]=cov[fa]=x;
	}
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (ei->to!=fa && cov[ei->to]!=cov[x])
			F.link(cov[x],cov[ei->to]);
	return 1;
}
int calc(int x,int fa){
	sz[x]=0;
	int pro=1;
	for (EDGE *ei=F.last[x];ei;ei=ei->las){
		pro=pro*calc(ei->to,x);
		sz[x]+=sz[ei->to];
		pro=pro*C[sz[x]][sz[ei->to]];
	}
	sz[x]++;
	return pro;
}
int main(){
	//freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for (int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		G.link(u,v),G.link(v,u);
	}
	for (int i=1;i<=n;++i)
		scanf("%d",&c[i]);
	C[0][0]=1;
	for (int i=1;i<=n;++i){
		C[i][0]=1;
		for (int j=1;j<=i;++j)
			C[i][j]=C[i-1][j-1]^C[i-1][j];
	}
	int ans=0;
	if (n&1){
		for (int x=1;x<=n;++x){
			F.clear();
			memset(cov,0,sizeof(int)*(n+1));
			if (!predfs(x,0))
				continue;
			ans^=calc(x,0)*c[x];
		}
	}
	else{
		for (int x=1;x<=n;++x)
			for (EDGE *ei=G.last[x];ei;ei=ei->las){
				int y=ei->to;
				if (x<y){
					F.clear();
					memset(cov,0,sizeof(int)*(n+1));
					cov[x]=cov[y]=y;
					if (!predfs(x,0))
						continue;
					int c_=!(c[x]&&c[y]);
					ans^=calc(y,0)*c_; 
				}
			}
	}
	printf("%d\n",ans);
	return 0;
}

推荐阅读