bzoj 2560 串珠子
阿新 • • 發佈:2018-11-22
Written with StackEdit.
Description
銘銘有\(n\)個十分漂亮的珠子和若干根顏色不同的繩子。現在銘銘想用繩子把所有的珠子連線成一個整體。
現在已知所有珠子互不相同,用整數\(1\)到\(n\)編號。對於第i個珠子和第j個珠子,可以選擇不用繩子連線,或者在\(c_{i,j}\)根不同顏色的繩子中選擇一根將它們連線。如果把珠子看作點,把繩子看作邊,將所有珠子連成一個整體即為所有點構成一個連通圖。特別地,珠子不能和自己連線。
銘銘希望知道總共有多少種不同的方案將所有珠子連成一個整體。由於答案可能很大,因此只需輸出答案對\(1000000007\)取模的結果。
Input
標準輸入。輸入第一行包含一個正整數\(n\),表示珠子的個數。接下來\(n\)行,每行包含\(n\)個非負整數,用空格隔開。這\(n\)行中,第\(i\)行第\(j\)個數為\(c_{i,j}\)。
Output
標準輸出。輸出一行一個整數,為連線方案數對\(1000000007\)取模的結果。
Sample Input
3
0 2 3
2 0 4
3 4 0
Sample Output
50
HINT
對於\(100\%\)的資料,\(n\)為正整數且\(n\leq16\),所有的\(c_{i,j}\)為非負整數且不超過\(1000000007\)。保證\(c_{i,j}=c_{j,i}\)。
Solution
- \(n\)較小,考慮狀壓\(dp\).
- 令\(f[S]\)表示集合\(S\)中的點聯通時的方案數,\(g[S]\)表示集合\(S\)中的數任意連邊時的方案數.那麼算出\(S\)中的點不連通的方案數和\(g[S]\)即可求出\(f[S]\).
- 有狀態轉移方程\(f[S]=g[S]−∑_{S^{'}∈S}f[S^{'}]∗g[S\)^\(S^{'}]\).
- 為了避免重複計數,需要固定一個點作為代表元素,即\(S^{'}\)中必須包含它.
#include<bits/stdc++.h> using namespace std; typedef long long LoveLive; inline int read() { int out=0,fh=1; char jp=getchar(); while ((jp>'9'||jp<'0')&&jp!='-') jp=getchar(); if (jp=='-') { fh=-1; jp=getchar(); } while (jp>='0'&&jp<='9') { out=out*10+jp-'0'; jp=getchar(); } return out*fh; } const int P=1e9+7; inline int add(int a,int b) { return (a + b) % P; } inline int mul(int a,int b) { return 1LL * a * b % P; } int fpow(int a,int b) { int res=1; while(b) { if(b&1) res=mul(res,a); a=mul(a,a); b>>=1; } return res; } int inv(int x) { return fpow(x,P-2); } const int MAXN=20; const int MAXS=(1<<20)+10; int c[MAXN][MAXN]; int n,m; int f[MAXS],g[MAXS]; //f是必須連通的方案數,g是隨便連邊的方案數 int main() { n=read(); m=(1<<n)-1; for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) c[i][j]=read(); for(int i=0;i<=m;++i) { g[i]=1;//初始化,未連邊 for(int j=1;j<=n;++j)//選擇j作為這個子集的代表元素 { if(i&(1<<(j-1)))//j在這個子集圖中 { for(int k=j+1;k<=n;++k) { if(i&(1<<(k-1))) g[i]=mul(g[i],c[j][k]+1); } } } } for(int i=1;i<=m;++i) { for(int S=i&(i-1);S;S=i&(S-1)) { if(!((i^S)&(i&(-i)))) f[i]=add(f[i],mul(f[S],g[i^S])); } f[i]=add(g[i],P-f[i]); } printf("%d\n",f[m]); return 0; }