题目描述
小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。
有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n?1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。
只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。
输入输出格式
输入格式:
第一行包含个2正整数n,m,表示原来的饰品中小星星的个数和细线的条数。接下来m行,每行包含2个正整数u,v,表示原来的饰品中小星星u和v通过细线连了起来。这里的小星星从1开始标号。保证u≠v,且每对小星星之间最多只有一条细线相连。接下来n-1行,每行包含个2正整数u,v,表示现在的饰品中小星星u和v通过细线连了起来。保证这些小星星通过细线可以串在一起。n<=17,m<=n*(n-1)/2
输出格式:
输出共1行,包含一个整数表示可能的对应方式的数量。如果不存在可行的对应方式则输出0。
输入输出样例
输入样例#1:
4 3
1 2 1 3 1 4 4 1 4 2 4 3
输出样例#1:
6
题解
首先看到n<=17,第一反应就是撞鸭
然后想了一会儿觉得是可行的,就是设\(f[i][j][S]\)表示在树上的点i对应图上的点j,且i的子树选取图上的点的情况是\(S\) 然后dp就直接无脑枚举这个点u对应图上的什么,然后再枚举点u的状态,然后再枚举v对应图上的什么,然后再枚举点v的状态,然后ta就\(MLE+TLE\)成70分了#include#include #include #include #include # define LL long longconst int M = 18 ;const int N = (1 << 17) + 1 ;using namespace std ;int n , m , size[M] ;vector < int > G[M] , vec[M] , sit[M] ;LL f[M][M][N] ;LL Ans ;inline int Gsz(int x) { int ret = 0 ; for(int i = 1 ; i <= n ; i ++) if(x & (1 << (i - 1))) ++ ret ; return ret ;}void dfs(int u , int father) { size[u] = 1 ; for(int i = 1 ; i <= n ; i ++) f[u][i][(1 << (i - 1))] = 1 ; for(int e = 0 , v ; e < vec[u].size() ; e ++) { v = vec[u][e] ; if(v == father) continue ; dfs(v , u) ; for(int i = 0 , S ; i < sit[size[u]].size() ; i ++) { S = sit[size[u]][i] ; for(int id = 1 ; id <= n ; id ++) { if(!(S & (1 << (id - 1)))) continue ; for(int j = 0 , T ; j < sit[size[v]].size() ; j ++) { T = sit[size[v]][j] ; if(S & T) continue ; for(int k = 0 , vid ; k < G[id].size() ; k ++) { vid = G[id][k] ; if(!(T & (1 << (vid - 1)))) continue ; f[u][id][S | T] += f[u][id][S] * f[v][vid][T] ; } } } } size[u] += size[v] ; }}int main() { scanf("%d%d",&n,&m) ; for(int i = 1 , u , v ; i <= m ; i ++) { scanf("%d%d",&u,&v) ; G[u].push_back(v) ; G[v].push_back(u) ; } for(int i = 1 , u , v ; i < n ; i ++) { scanf("%d%d",&u,&v) ; vec[u].push_back(v) ; vec[v].push_back(u) ; } for(int i = 0 ; i < (1 << n) ; i ++) sit[Gsz(i)].push_back(i) ; dfs(1 , 1) ; for(int i = 1 ; i <= n ; i ++) Ans += f[1][i][(1 << n) - 1] ; printf("%lld\n",Ans) ; return 0 ;}
然后这么做复杂度显然不对,我们考虑一个正确的方法
我们考虑这道题用撞鸭的目的是什么? 判重! 就是为了每个点映射图上的点不要重复 所以我们可以考虑容斥 先让每个点随便对应图上的点,只要能对应上就行(两点之间有边),然后减去有一个点被选重复的方案数,再加上有两个点被选重复的方案数-...+..,以此类推 复杂度就是\(2^n*n^3\)#include#include # define LL long longconst int M = 18 ;using namespace std ;int n , m , Sit ;vector < int > G[M] , vec[M] ;LL f[M][M] , Ans ;void dfs(int u , int father) { for(int i = 1 ; i <= n ; i ++) f[u][i] = 1 ; for(int i = 0 , v ; i < vec[u].size() ; i ++) { v = vec[u][i] ; if(v == father) continue ; dfs(v , u) ; for(int id = 1 ; id <= n ; id ++) { if(Sit & (1 << (id - 1))) continue ; LL temp = 0 ; for(int j = 0 , idv ; j < G[id].size() ; j ++) { idv = G[id][j] ; if(Sit & (1 << (idv - 1))) continue ; temp += f[v][idv] ; } f[u][id] *= temp ; } }}inline void query(int ret) { dfs(1 , 1) ; for(int i = 1 ; i <= n ; i ++) { Ans += ret * f[1][i] ; for(int j = 1 ; j <= n ; j ++) f[j][i] = 0 ; }}int main() { scanf("%d%d",&n,&m) ; for(int i = 1 , u , v ; i <= m ; i ++) { scanf("%d%d",&u,&v) ; G[u].push_back(v) ; G[v].push_back(u) ; } for(int i = 1 , u , v ; i < n ; i ++) { scanf("%d%d",&u,&v) ; vec[u].push_back(v) ; vec[v].push_back(u) ; } for(int i = 0 , cnt = 0 ; i < (1 << n) ; i ++) { cnt = 0 ; Sit = i ; for(int j = 1 ; j <= n ; j ++) if(i & (1 << (j - 1))) ++ cnt ; cnt % 2 ? query(-1) : query(1) ; } printf("%lld\n",Ans) ; return 0 ;}