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

。_饼干妹妹 提交于 2019-11-30 02:21:06

 

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

(完)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!