P4208 [JSOI2008]最小生成树计数
讲课的时候提到了这个妙极了的思想,于是就把这题做了。
有个结论:相同边权的边集不论怎么选,对最小生成树的贡献都相同(选的数量都会相同,而且连完之后图的连通性也会相同)
所以可以把不同边权的边分开来看,答案就是每种边权的方案之积。
于是就可以愉快地暴力了:
- 从小到大枚举边权;
- 拿出这个边权的所有边,并算出要连多少条边,以及连接这些边会连通哪些点;
- 暴力枚举选哪些边,加入方案里;
- 最后把现在通的集合缩起来。
复杂度上界是(O(m 2^{10} n)),然而远远达不到,因为(2^{10})最大也就是(C ^{5}_{10}),而且因为要缩点,n也不会一直是n。
emmm还有,注意判断无解情况
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cctype>
using namespace std;
typedef int mmp;
#define vd void
#define rg register
#define il inline
#define sta static
il int gi(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return x*f;
}
namespace YYB{
int n,m;
struct edge{int u,v,w;}yyb[1001];
int operator <(const edge&a,const edge&b){return a.w<b.w;}
int fa[101];
il int find(int*fa,int x){return fa[x]==x?x:fa[x]=find(fa,fa[x]);}
il bool Union(int*fa,int x,int y){
x=find(fa,x),y=find(fa,y);
if(x>y)swap(x,y);
if(x!=y){fa[y]=x;return 1;}
return 0;
}
il vd solve(){
n=gi(),m=gi();
for(rg int i=1;i<=m;++i)yyb[i].u=gi(),yyb[i].v=gi(),yyb[i].w=gi();
sort(yyb+1,yyb+m+1);
for(rg int i=1;i<=n;++i)fa[i]=i;
int ans=1;
for(rg int i=1;i<=m;++i){
int j=i;while(yyb[j+1].w==yyb[i].w)++j;
for(rg int s=i;s<=j;++s)yyb[s].u=find(fa,yyb[s].u),yyb[s].v=find(fa,yyb[s].v);
int fafa[101],tot=0;
for(rg int s=1;s<=n;++s)fafa[s]=s;
for(rg int s=i;s<=j;++s)if(Union(fafa,yyb[s].u,yyb[s].v))++tot;
int res=0;
for(rg int s=0;s<1<<(j-i+1);++s){
int cnt=0;for(rg int p=s;p;p-=p&-p)++cnt;
if(cnt!=tot)continue;
for(rg int q=1;q<=n;++q)fafa[q]=q;
int tot_=0;
for(rg int q=i;q<=j;++q)
if(s&(1<<q-i))
if(Union(fafa,yyb[q].u,yyb[q].v))++tot_;
if(tot_==tot)++res;
}
for(rg int s=i;s<=j;++s)Union(fa,yyb[s].u,yyb[s].v);
ans=ans*res%31011;
i=j;
}
for(rg int i=1;i<=n;++i)if(find(fa,i)!=1)ans=0;
printf("%d
",ans);
}
}
mmp main(){
//freopen("bzoj_1016.in","r",stdin);
//freopen("bzoj_1016.out","w",stdout);
YYB::solve();
return 0;
}