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