zoukankan      html  css  js  c++  java
  • AGC050F NAND Tree

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

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

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

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


    神仙题。

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

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

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

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

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

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

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

    结合观察1和观察2,问题变成每次选择个(x o y o 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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    mysql u root p 等这些常用的参数
    关于类对象大小的 sizeof 计算问题
    sql like用法
    链表队列
    Zend Studio安装教程
    #define和inline 的区别
    ERROR 1045 (28000): Access denied for user root@localhost (using password: NO)
    Ubuntu 10.04下LAMP环境的配置与使用教程
    win7保护视力的电脑设置
    sql between and 用法
  • 原文地址:https://www.cnblogs.com/jz-597/p/14993218.html
Copyright © 2011-2022 走看看