第二题 题解
首先可以想到,先对于所有的连通块进行计算贡献
运用乘法原理,将每个连通块的贡献相乘
定义一个连通块的贡献为它的可行方案数,下面开始分类讨论
对于存在环的连通块
这边发现,右边的环中,每个点都必须收入一条边,有两种可行方案
所以
易得:对于一个连通块,若块中存在环,则贡献为
对于不存在环的连通块
可以发现,这类连通块有
一定存在一个点是没有被分到边的
在
对于边数大于点数的连通块
一定存在一个边没有被分配到点,不符合要求,方案为
所以遇到这种连通块时,直接全局判
现在问题转化为了:对于每个连通块,判断是否存在环,及点数与边数的关系
对于判环,也可以转化为点数与边数的关系:
随便作一个环,就可以发现,环中的点数 = 边数
而边数可通过
整理得到:
对于每一个连通块:
点数 > 边数: 为无环,贡献为点数
点数 = 边数: 为有环,贡献为 2
点数 < 边数: 不可行,答案为 0
最后将每个连通块的贡献相乘即可
代码:
#define loop(i,x,y) for(int i=x;i<=y;i++)
#define doop(i,x,y) for(int i=x;i>=y;i--)
using namespace std;
const int Mod=1000000007;
const long long N=1e6+5;
ll n,m,xx,yy;
ll to[N],hd[N],nxt[N],tot;
void add(ll u,ll v){
nxt[++tot]=hd[u];
to[tot]=v;
hd[u]=tot;
}
ll edg,pts;
void dfs(ll p){
vis[p]=1,pts++;
for(int i=hd[p];~i;i=nxt[i],edg++){
if(!vis[to[i]]) dfs(to[i]);
}
}
signed main(){
memset(hd,-1, sizeof hd);
n=read(),m=read();
loop(i,1,m){
xx=read(),yy=read();
add(xx,yy),add(yy,xx);
}
ll ans=1;
loop(i,1,n){
if(!vis[i]){
pts=edg=0;
dfs(i);
if(pts>edg/2) ans=(ans*pts)%Mod;
else if(pts==edg/2) ans=(ans*2)%Mod;
else {ans=0;break;}
}
}
cout<<ans;
return 0;
}