Newcoder 147 B.Enumeration not optimization(狀壓DP+樹形DP)
阿新 • • 發佈:2018-12-10
Description
給出一個個點條邊的無向圖,邊有邊權,對於這張圖的任意一個有根生成樹,定義其權值為 其中為點在生成樹中的深度,統計這張圖所有有根生成樹權值之和,結果模
Input
第一行輸入兩個整數表示點數和邊數,之後行每行三個整數表示之間有一條權值為的邊
Output
輸出這張圖所有有根生成樹權值之和,結果模
Sample Input
4 5 1 2 1 1 3 3 1 4 1 2 3 4 3 4 1
Sample Output
303
Solution
考慮每條邊對答案的貢獻,貢獻分為兩部分,在生成樹中是的父親節點以及是的父親節點,以是的父親節點為例,斷掉這條邊將樹分成兩個連通塊,記所在連通塊為,所在連通塊為,此時需要求出以為根的子樹方案數,而節點的深度和實際上取決於在點集中的深度,以表示在狀態的所有子樹中的深度加一的和,那麼我們只要求出,即得答案為
現在考慮狀壓求,列舉點集和中一點,下面要把分解為兩個不交的集合,因為涉及到計數,故要有順序,否則會記重,令為包含點集中最小編號的集合,為在中的補,列舉中一點,考慮通過將通過邊合併進行對狀態的轉移,首先有轉移 其中表示邊的數量,主要考慮求,邊存在狀態有兩種:
1.是的父親,那麼此時的深度不變,有轉移 2.是的父親,此時的深度為的深度加一,的深度加一之和為,每種方案的深度要再加一才是在這種方案下的深度,點集構成一棵樹的方案數為,故有轉移 Code
#include<cstdio>
using namespace std;
typedef long long ll;
#define mod 1000000007
int mul(int x,int y)
{
ll z=1ll*x*y;
return z-z/mod*mod;
}
int add(int x,int y)
{
x+=y;
if(x>=mod)x-=mod;
return x;
}
#define maxn (1<<12)
int n,m,e[5005][3],cnt[maxn],num[13][13],f[maxn][13],g[maxn][13];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);
e[i][0]--,e[i][1]--;
num[e[i][0]][e[i][1]]++,num[e[i][1]][e[i][0]]++;
}
int N=(1<<n)-1;
for(int i=1;i<=N;i++)cnt[i]=cnt[i/2]+(i&1);
for(int S=1;S<=N;S++)
for(int u=0;u<n;u++)
if((S>>u)&1)
{
int SS=S^(1<<u);
int temp=SS&(-SS);
if(!SS)
{
f[S][u]=g[S][u]=1;
continue;
}
for(int T=SS;;T=(T-1)&SS)
{
if(T&temp)
for(int v=0;v<n;v++)
if((T>>v)&1)
{
int W=S^T;
int res=add(mul(f[W][u],g[T][v]),mul(add(f[T][v],mul(cnt[T],g[T][v])),g[W][u]));
f[S][u]=add(f[S][u],mul(res,num[u][v]));
g[S][u]=add(g[S][u],mul(mul(g[W][u],g[T][v]),num[u][v]));
}
if(!T)break;
}
}
int ans=0;
for(int i=0;i<m;i++)
{
int u=e[i][0],v=e[i][1],w=e[i][2];
for(int S=1<<u;S<=N;S=(S+1)|(1<<u))
ans=add(ans,mul(w,add(mul(f[S][u],g[N^S][v]),mul(g[S][u],f[N^S][v]))));
}
printf("%d\n",ans);
return 0;
}