zoukankan      html  css  js  c++  java
  • 【题解】 「HNOI2015」实验比较 树形dp+二项式反演 LOJ2117

    Legend

    Link ( extrm{to LOJ})

    Editorial

    这是一个 (O(n^2)) 的做法。

    题意各位大佬都讲得很清楚了,现在可以转化成一个问题是:给定一个内向森林,祖先结点的颜色必须严格大于后代结点。颜色必须为连续正整数,求方案。

    先考虑一棵树的答案:

    (g(n)) 表示所有节点有 (n) 种颜色且满足题意的方案数。

    然后我们可以设 (f(n)= sumlimits_{i=0}^{n}inom{n}{i}g(i))

    21.1.13upd:这样看着可能不太明确其含义,补一下:我们可以设 (f[i][j]) 表示在 (i) 子树内的所有节点,颜色小于 (j)(i) 的颜色恰好是 (j) 的方案数。即,不要求选出的颜色的值域是 (1sim k) 连续的。这样就可以树形 dp 了。

    不难发现 (f(i) (1 le i le n)) 可以通过一个 (O(n^2)) 的前缀和优化树形 dp 求出。

    根据二项式反演 (g(n)=sumlimits_{i=0}^{n} inom{n}{i}(-1)^{n-i}f(i)) 我们就可以得到答案了。

    总复杂度仅为 (O(n^2))


    Code

    而在本题中,是内向树森林,我们建立一个虚结点 (n+1),与所有树的根连边。

    运行上述算法时,我们对虚结点的转移进行单独判断即可。

    int ind[MX];
    int head[MX] ,tot;
    struct edge{
    	int node ,next;
    }h[MX << 1];
    void addedge(int u ,int v){
    	h[++tot] = (edge){v ,head[u]} ,head[u] = tot;
    	ind[v]++;
    }
    
    LL dp[MX][MX] ,S[MX][MX] ,C[MX][MX];
    
    int fa[MX] ,n ,m;
    void init(){
    	for(int i = 1 ; i < MX ; ++i) fa[i] = i;
    	for(int i = 0 ; i < MX ; ++i) C[i][0] = 1;
    	for(int i = 1 ; i < MX ; ++i)
    		for(int j = 1 ; j < MX ; ++j)
    			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
    }
    int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
    void link(int u ,int v){u = find(u) ,v = find(v) ,fa[u] = v;}
    
    int cnt ,ins[MX];
    void dapai(int x){
    	if(ins[x]) puts("0") ,exit(0);
    	ins[x] = 1;
    	for(int i = 1 ; i <= cnt ; ++i) dp[x][i] = 1;
    	for(int i = head[x] ,d ; i ; i = h[i].next){
    		dapai(d = h[i].node);
    		for(int j = 1 ; j <= cnt ; ++j){
    			if(x == n + 1) dp[x][j] = dp[x][j] * S[d][j] % MOD;
    			else dp[x][j] = dp[x][j] * S[d][j - 1] % MOD;
    		}
    	}
    	ins[x] = 0;
    	for(int i = 1 ; i <= cnt ; ++i) S[x][i] = (S[x][i - 1] + dp[x][i]) % MOD;
    }
    
    int ecnt;
    struct EDGE{int u ,v;}E[MX];
    
    int main(){
    	init();
    	n = read() ,m = read();
    	for(int i = 1 ,u ,v ; i <= m ; ++i){
    		char type[233];
    		scanf("%d%s%d" ,&u ,type ,&v);
    		if(type[0] == '=') link(u ,v);
    		else E[++ecnt] = (EDGE){u ,v};
    	}
    	for(int i = 1 ; i <= ecnt ; ++i){
    		addedge(find(E[i].u) ,find(E[i].v));
    	}
    
    	int in = 0;
    	for(int i = 1 ; i <= n ; ++i){
    		if(find(i) == i) ++cnt;
    		if(find(i) == i && !ind[i]){
    			addedge(n + 1 ,find(i));
    			++in;
    		}
    	}
    	if(!in){
    		puts("0");
    		return 0;
    	}
    	dapai(n + 1);
    	LL ans = 0;
    	for(int j = 1 ; j <= cnt ; ++j){
    		LL tmp = 0;
    		for(int i = 0 ; i <= j ; ++i){
    			LL add = dp[n + 1][i] * ((j - i) & 1 ? -1 : 1) * C[j][i] % MOD;
    			add = (add + MOD) % MOD;
    			tmp = (tmp + add) % MOD;
    		}
    		debug("stained %d color %lld
    " ,j ,tmp);
    		ans = (ans + tmp) % MOD;
    	}
    	printf("%lld
    " ,ans);
    	return 0;
    }
    
  • 相关阅读:
    [C++] 用Xcode来写C++程序[5] 函数的重载与模板
    【转】字符编码详解——彻底理解掌握编码知识,“乱码”不复存在
    【转】无法将notepad++添加到打开方式列表中的解决办法
    【转】关于启用 HTTPS 的一些经验分享
    【转】GPU 与CPU的作用协调,工作流程、GPU整合到CPU得好处
    【转】excel 末尾是0 恢复数据方法
    【转】怎么让VS2015编写的程序在XP中顺利运行
    【转】深入 Docker:容器和镜像
    【转】SSL/TLS协议运行机制的概述
    【转】C++怎么读写windows剪贴板的内容?比如说自动把一个字符串复制.
  • 原文地址:https://www.cnblogs.com/imakf/p/14273844.html
Copyright © 2011-2022 走看看