题解 P1657 【选书】
这题用图论完全可以
首先建图:对于每个人,将他喜欢的两本书的编号连一条无向边
这样就得到了一个
然后考虑答案如何计算
- 答案非0,当且仅当这个图的每个连通块内,点的数量和边的数量相同
证明:
(必要性)如果存在一个连通块,块内点的数量大于边的数量(可以看出,这个连通块只能是树),那么无论如何选择边的方向,总有至少一个点入度为0。
而如果存在一个连通块,块内边的数量大于点的数量,那么剩余部分点的数量大于边的数量,可以得出有至少存在一个连通块,块内点的数量大于边的数量
(充分性)可以看出,每个连通块都是一个环或基环树。
对于每个连通块,这样构造:将每个度为1的节点对应的边指向这个点,对剩余部分继续构造,这样最后只剩一个环,而这个环上只要都朝一个方向就行了
大致思路就是把叶子节点一个个撸下来,最后只剩一个环
- 答案非0时,如果这个图有
k 个连通块,那么答案就是2^k
由上面充分性的证明,处理不在环上的点时,只有唯一的一种构造方案,而处理环时,有两个方向,并且各个连通块独立。由乘法原理不难得出答案
然后就可以
关于出题人说的造数据难,大概就是因为没有发现这些特点吧QWQ
具体做法:
-
先把
n=0 的情况特判掉(答案为0) -
建图,dfs一遍,记录每个点所属的连通块
-
判断每个连通块内边数是否等于点数
-
统计连通块数目,计算答案
其中第三步我是这样做的:计算连通块内点的度数之和,如果不满足度数之和是点个数之和的二倍,则答案为0。这应该很显然吧QWQ
最后放代码:
注:这里默认对998244353取模了,对于这个题的数据没问题
#include<cstdio>
long long read(){
long long n=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){n=n*10+c-'0';c=getchar();}
return n;
}
const int N=300007;
#define next next2
int head[N],next[N*2],ver[N*2],tot;
int gid[N];
int deg[N];
int cnt[N],cdeg[N];
void add(int a,int b){
deg[a]++;
deg[b]++;
tot++;
ver[tot]=b;
next[tot]=head[a];
head[a]=tot;
tot++;
ver[tot]=a;
next[tot]=head[b];
head[b]=tot;
}
void dfs(int id,int p){
gid[id]=p;
for(int i=head[id];i;i=next[i]){
if(gid[ver[i]])continue;
dfs(ver[i],p);
}
}
int n,a,b;
long long ans=1;
int main(){
n=read();
if(n==0){
printf("0\n");
return 0;
}
for(int i=1;i<=n;i++){//建图
a=read();
b=read();
add(a,b);
}
for(int i=1;i<=n;i++){//DFS求每个点所属的连通块编号
if(!gid[i])dfs(i,i);
}
for(int i=1;i<=n;i++){//统计每个连通块内的信息
cnt[gid[i]]++;
cdeg[gid[i]]+=deg[i];
}
for(int i=1;i<=n;i++){
if(cnt[i]*2!=cdeg[i]){//判断不合法情况
printf("0\n");
return 0;
}
if(cnt[i]!=0)ans=ans*2%998244353;//计算答案
//注:这里cnt[i]=0表示这个编号没有被使用,也就是不算一个连通块
}
printf("%lld\n",ans);
}