JSOI2008 最小生成樹計數
題目解析
剛開始想到了加邊然後破圈法,但我似乎不會統計答案。
有個結論:所有\(MST\)中,同一權值的邊的個數是不會變的。
簡單說明一下:
我們想想\(Kruskal\)的演算法流程,是按照邊權從小到大進行排序加邊,然後直到整個圖聯通我們就得到了\(MST\)。如果再選一個\(MST\)出來,假設我們刪掉其中一條邊,為了滿足權值的要求,我們必須找一個權值恰好等於這條邊的邊來替代它。
而會不會出現我去掉兩條邊,然後加一條更小的邊,再加一條更大的邊,最後的結果是一樣的呢?不會出現這種情況的。如果去掉的這兩條邊,可以用後面加的這兩條邊代替的話,而我們知道每斷開\(MST\)上一條邊,就會把樹變成兩半,那麼為了保持樹的形態,更小的那條邊應該是可以替代被換下來的兩條邊的某一條邊的(指連通性),那麼這樣可以得到一個更小的\(MST\)
接下來我們可以利用這個結論乾點事情:
(而且題目也說了具有相同權值的邊不會超過10條,複雜度不會超過\(2^{10}\times \frac{1000}{10}\)
我們可以針對每種權值的邊進行搜尋:要不要這條邊?類似於求\(MST\)的過程,在\(MST\)裡的邊都承擔起聯通兩個連通塊的功能,所以如果一條邊的兩個結點不在同一連通塊裡,可以選擇要/不要這條邊,否則就只能不要這條邊。這裡要注意回溯的寫法(詳見程式碼
(為了方便描述,我下面用“顏色”來表示離散化後的邊的權值
如果一個方案的這種顏色的邊的條數恰好等於\(MST\)中這個顏色的邊的條數,那麼這就是一個合法方案。所有顏色合法方案的乘積就是答案。
還可以有一個小剪枝:如果當前加入的邊的條數大於了\(MST\)裡的邊的條數,顯然不合法,\(return\)掉。
在算完一種顏色的邊之後,要把這種顏色的邊全部並在一起,保證後面的連通性判斷正確,這個順序應該是“無傷大雅”的,因為我們知道每種顏色有多少條邊在\(MST\)裡面,加了那麼多條邊之後就可以了,畢竟我們只需要\(MST\)中的任意一種情況來判斷連通性。
►Code View
#include<cstdio> #include<algorithm> #include<vector> #include<queue> #include<cstring> using namespace std; #define N 105 #define M 1005 #define MOD 31011 #define INF 0x3f3f3f3f #define LL long long int rd() { int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f*x; } struct node{ int u,v,w; }edge[M]; int n,m,f[N]; pair<int,int> a[M];//每種權值的邊的範圍 int c[M]/*第i條邊的顏色(權值離散化下標)*/,tot/*權值總數*/; int num[M];//第i種權值的邊的條數 int ans=1,res; bool cmp(node p,node q) { return p.w<q.w; } void Init() { for(int i=1;i<=n;i++) f[i]=i; } int Find(int x) { if(f[x]==x) return x; return f[x]=Find(f[x]); } bool Union(int u,int v) { u=Find(u),v=Find(v); if(u==v) return 0; if(u<v) f[u]=v; else f[v]=u; return 1; } void Kruskal() { Init(); int cnt=0,sum=0; for(int i=1;i<=m;i++) { if(Union(edge[i].u,edge[i].v)) { num[c[i]]++; cnt++; sum+=edge[i].w; } if(cnt==n-1) break; } if(cnt!=n-1) { puts("0"); exit(0); } } void dfs(int i,int k,int x) {//當前搜到第i條邊 已有k條邊 為第x種權值 if(k>num[x]) return ; if(i==a[x].second+1) { if(k==num[x]) res++; return ; } int p[N];//不能定義成全域性變數 遞迴搜尋下去之後值就變了 for(int j=1;j<=n;j++) p[j]=f[j]; if(Union(edge[i].u,edge[i].v)) dfs(i+1,k+1,x); for(int j=1;j<=n;j++) f[j]=p[j]; dfs(i+1,k,x); } int main() { n=rd(),m=rd(); for(int i=1;i<=m;i++) edge[i].u=rd(),edge[i].v=rd(),edge[i].w=rd(); sort(edge+1,edge+m+1,cmp); c[1]=++tot,a[1].first=a[1].second=1; for(int i=2;i<=m;i++)//找同一權值的邊的範圍 { if(edge[i].w==edge[i-1].w) a[tot].second++,c[i]=tot; else { c[i]=++tot; a[tot].first=a[tot].second=i; } } Kruskal(); Init(); for(int i=1;i<=tot;i++) if(num[i]) { res=0; dfs(a[i].first,0,i); ans=ans*res%MOD; int cnt=0; for(int j=a[i].first;j<=a[i].second;j++) { if(Union(edge[j].u,edge[j].v)) cnt++; if(cnt==num[i]) break; } } printf("%d\n",ans); return 0; }