1. 程式人生 > 實用技巧 >JSOI2008 最小生成樹計數

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;
}