zoukankan      html  css  js  c++  java
  • @loj


    @description@

    小 Y 是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有 n 颗小星星,用 m 条彩色的细线串了起来,每条细线连着两颗小星星。
    有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了 n-1 条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。
    小 Y 找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。
    小 Y 想知道有多少种可能的对应方式。只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。

    输入格式
    第一行包含两个正整数 n,m,表示原来的饰品中小星星的个数和细线的条数。
    接下来 m 行,每行包含两个正整数 u,v,表示原来的饰品中小星星 u 和 v 通过细线连了起来。
    这里的小星星从 1 开始标号。保证 u ≠ v,且每对小星星之间最多只有一条细线相连。 接下来 n-1 行,每行包含两个正整数 u,v,表示现在的饰品中小星星 u 和 v 通过细线连了起来。保证这些小星星通过细线可以串在一起。

    输出格式
    输出共一行,包含一个整数表示可能的对应方式的数量。

    如果不存在可行的对应方式则输出 0。

    样例输入
    4 3
    1 2
    1 3
    1 4
    4 1
    4 2
    4 3
    样例输出
    6

    数据范围与提示
    对于所有的数据,n <= 17, m <= n*(n-1)/2。

    @solution@

    普通的做法人人都会:定义 dp[i][j][s] 表示以 i 为根的子树,i 对应 j,这棵子树内已经用了集合 s 的点。
    然后一波枚举子集转移。然后就炸了。
    我没试过 FWT 行不行,不过好像比下面的算法多个 n 的复杂度,所以估计过不了。

    考虑抽象问题模型:我们要找的其实是一个满足 “树边的两个端点对应过去也存在边” 这一限制的置换。
    问题在于,置换必须要满足每个元素恰好被对应一次,所以我们才要枚举子集啊之类。
    但其实只有一条边相邻两个点才会产生限制。因此我们直接统计置换是很吃亏的。

    我们可以把置换看成 n 个点都要被对应,通过容斥转成只有 m 个点可以被对应。
    形式化来说,假如我们可被对应的点的集合为 S。
    我们的原问题相当于 S 中每一个点都要被对应,通过容斥转为 T 中的点可以被对应,其中 (Tsubset S)

    那么就回到我们一开始的树形 dp,只是少了 s 这一维,变成 dp[i][j] 表示以 i 为根的子树,i 对应 j 的方案数。

    外层容斥 (O(2^n)),树形 dp 的复杂度为 (O(n^3)),所以总时间复杂度为 (O(2^n*n^3))

    @accepted code@

    #include<cstdio>
    #include<vector>
    using namespace std;
    typedef long long ll;
    vector<int>G[20];
    void addedge(int u, int v) {
    	G[u].push_back(v);
    	G[v].push_back(u);
    }
    bool tag[20];
    int A[20][20], n, m;
    ll dp[20][20];
    void dfs(int x, int f) {
    	for(int i=0;i<G[x].size();i++)
    		if( G[x][i] != f ) dfs(G[x][i], x);
    	for(int i=0;i<n;i++)
    		if( tag[i] ) {
    			dp[x][i] = 1;
    			for(int j=0;j<G[x].size();j++) {
    				int p = G[x][j];
    				if( p == f ) continue;
    				ll del = 0;
    				for(int k=0;k<n;k++)
    					if( A[i][k] ) del += dp[p][k];
    				dp[x][i] *= del;
    			}
    		}
    		else dp[x][i] = 0;
    }
    int f[1<<20];
    int main() {
    	scanf("%d%d", &n, &m);
    	for(int i=1;i<=m;i++) {
    		int u, v; scanf("%d%d", &u, &v), u--, v--;
    		A[u][v] = A[v][u] = true;
    	}
    	for(int i=1;i<n;i++) {
    		int u, v; scanf("%d%d", &u, &v), u--, v--;
    		addedge(u, v);
    	}
    	int t = (1<<n); f[0] = 1;
    	for(int i=1;i<t;i++)
    		f[i] = (i&1) ? -f[i>>1] : f[i>>1];
    	ll ans = 0;
    	for(int s=0;s<t;s++) {
    		for(int i=0;i<n;i++)
    			tag[i] = !((s>>i) & 1);
    		dfs(0, -1);
    		for(int i=0;i<n;i++)
    			if( tag[i] ) ans = ans + f[s]*dp[0][i];
    	}
    	printf("%lld
    ", ans);
    }
    

    @details@

    ZJOI 的题都是神仙题 * 2。

    代码倒是非常简洁。

  • 相关阅读:
    周易:简易、变易、不易
    2018.net面试题汇总
    关于《推荐系统实践》
    设计模式指引
    facebook的工程开发,不得不佩服
    eclipse自动补全的设置
    数据智慧工程师——计算机和人类之间的中间人——如何从数据中获取有价值的知识
    (移动位置社会网络中)LBSN:好友关系对人类活动的影响分析
    加快软件开发速度,eclipse最常用的快捷键
    人生是一对一的搏斗
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11653244.html
Copyright © 2011-2022 走看看