P3043 [USACO12JAN]牛联盟(并查集+数学)

(m<n<=1e5,有重边)

 题目表述有问题.....

给定一张图(不一定联通),每条边可以选择连接的两个点之一,剩余的点可以自己成对,问方案数。

一开始是真的被吓到了....觉得可写性极低的一题.....

但是两个结论如果推出来的话就蛮好的了

solution:

一开始想:对于每个块进行大小统计,然后组合数乘在一起。但是,有点麻烦:

有环的情况:对于一个联通块有环,那么就会有n个点,n条边,那就意味着会有一个联通块只有一个单独的点。单独考虑环块(下统称环块)

看看这个三元环(误),先确定第一条边选左或右两个点,如果第一个边确定了自己的选择,那么它就会占用下一条边的一个选择的权利,也就是:

如果确定了一条边,就可以确定整个环上的方案数:2

如果不是裸环的环块呢?

看这个:

同上,确定了环上的两个,链的一端就会被占用,也就是说:

只要是环块,对答案的贡献就是2!

(伟大的发现)

考虑无环的情况:

一共n个点,n-1条边(无向图,不能有环就是树),一共有n种方案。感性证明一下(不知道该怎么理性):

最后一个点不被选,而这个不被选的点一共有n种情况,这就是n种方案。

最后根据乘法原理,把所有的方案数乘起来就是ans。

综上结论,题目就变成了:给定一张图,判断联通块大小,找联通块里的环,统计答案

联通块?我有并查集!

找环?我有tarjan!

(旁边两位z姓大佬给我闷头两巴掌,这题还tarjan?你想变成zwjdd长度???)

这里介绍一种无向图判环方法(注意,不是找环,只能判断存在

还是使用并查集,原理就是在一个联通块内,如果遍历边的次数=点的个数,那么这里就存在环。

貌似是这样哈....我太弱了

代码(注释版):

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const int mod=1e9+7;
long long f[maxn],num[maxn],n,m,ans=1,c[maxn];
long long find(long long x){return f[x]==x?x:f[x]=find(f[x]);}//被大佬逼的一行冰茶姬
//variable declare:num[]:联通块大小,f[]不解释,c[]联通块内边的数量
int main() { scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) f[i]=i,num[i]=1;//初始化 for(int i=1;i<=m;i++) { long long x,y; scanf("%lld%lld",&x,&y); long long fa=find(x); long long fb=find(y); if(fa!=fb)//合并 { f[fa]=fb; num[fb]+=num[fa];//把联通块大小合并 num[fa]=0;//清零联通块大小 c[fb]+=c[fa]+1;//联通块内多了一条边 } else { c[fb]++;//否则多了一条非树边 } } for(int i=1;i<=n;i++) { if(f[i]==i&&c[i]<num[i])//如果边数没有点数多 ans=ans*num[i]%mod;//统计答案 else if(f[i]==i&&c[i]>=num[i])//否则就是有环,直接*2 ans=(ans<<1)%mod;//记得续命 } printf("%lld",ans);//longlong又出锅了 return 0; }

(完)

猜你喜欢

转载自www.cnblogs.com/ajmddzp/p/11546487.html