[zjoi2017]仙人掌
前言
谨以此题纪念我第一次参加省选时刚了5h这一题得到0分的经历
题目相关
链接
题目大意
给出仙人掌定义:如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌
给出一个图,求有多少种加边方式使得图成为一个仙人掌
数据范围
多组数据
∑n≤5∗105,∑m≤106\sum n\le5*10^5,\sum m\le10^6∑n≤5∗105,∑m≤106
题解
首先,如果这幅图本身就已经不是仙人掌了,那么输出000
如何判断呢?我们发现,建圆方树的时候,两条圆点的连边最多只会被删除一次,如果被删除了多次那么说明这幅图不是仙人掌
对于仙人掌的简单环上的边,我们一定不可以进行一条连边使得两个端点之间有一条路径经过这条边(因为这样就会出现一条边在多个简单环中的情况)
那么我们就直接把仙人掌简单环上的边直接删除,容易发现剩下的都是一些树了
对于树的情况,我们发现有两个东西不好处理:一个是最终的图中可以有边不在简单环上,二是添加的边不能有重边
我们发现这两个问题合在一起就被解决了,我们可以转化成:每条边都必须要在一个简单环上,允许有重边。容易发现,这么转化的答案不变
现在相当于是对于一棵树,求其被链覆盖的方案数
考虑树形dp
我们定义fif_ifi为以iii节点所在子树及其通往父亲的边被链覆盖的方案数
设sonuson_usonu为uuu节点的儿子集合,S(n)S(n)S(n)为nnn个点、每个点的度数小于等于111的图的数量
我们发现fu=S(∣sonu∣+1)∗∏v∈sonufvf_u=S(|son_u|+1)*\prod_{v\in son_u}f_vfu=S(∣sonu∣+1)∗v∈sonu∏fv
特殊的,我们发现根节点没有父亲边,这样的话其fif_ifi的定义不包含通往父亲的边,那么S(x)S(x)S(x)里面的xxx就是儿子数量了
我们现在考虑怎么求S(n)S(n)S(n)
我们考虑已知S(1)S(1)S(1)~S(n)S(n)S(n),怎么求S(n+1)S(n+1)S(n+1)
我们考虑第n+1n+1n+1个点
如果它不连边,那么方案数为S(n)S(n)S(n)
如果它连边,在前面nnn个点中选择一个连,那么方案数就为S(n−1)∗nS(n-1)*nS(n−1)∗n
即得到递推式S(n+1)=S(n)+n∗S(n−1)S(n+1)=S(n)+n*S(n-1)S(n+1)=S(n)+n∗S(n−1)
这样,我们就能O(n)\mathcal O(n)O(n)的完成此题
代码
#include
#include
#include
#include
namespace fast_IO
{const int IN_LEN=1000000,OUT_LEN=1000000;char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
#define rg register
typedef long long ll;
template<typename T>inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template<typename T>inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template<typename T>inline void print(const T x){if(x>=0)printe(x);else putchar('-'),printe(-x);}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
const int maxn=500005,maxm=2000005,mod=998244353;
int T,n,m;
int head[maxn],nxt[maxm],tow[maxm],tmp;bool del[maxm],DIE,VVV[maxm],vis[maxn];
inline void addb(const int u,const int v)
{tmp++;nxt[tmp]=head[u];head[u]=tmp;tow[tmp]=v;del[tmp]=VVV[tmp>>1]=0;
}
int stack[maxn],top,H;
void dfs(const int u)
{if(DIE)return;vis[u]=1;for(rg int i=head[u];i&&!DIE;i=nxt[i])if(!VVV[i>>1]){VVV[i>>1]=1;const int v=tow[i];stack[++top]=i;if(vis[v]){H=top;for(rg int j=top-1;j&&tow[stack[j]]!=v;j--)H=j;for(rg int j=top;j>=H;j--){const int id=stack[j];if(del[id])DIE=1;del[id]=del[id^1]=1;}}else dfs(v);top--;}
}
ll S[maxn],f[maxn],ans;
void DFS(const int u,const int fa)
{int size=0;f[u]=1,vis[u]=1;for(rg int i=head[u];i;i=nxt[i])if(!del[i]){const int v=tow[i];if(v!=fa)DFS(v,u),f[u]=f[u]*f[v]%mod,size++;}if(fa)size++;f[u]=f[u]*S[size]%mod;
}
int main()
{S[0]=S[1]=1;for(rg int i=2;i<=500000;i++)S[i]=(S[i-1]+S[i-2]*(i-1))%mod;read(T);while(T--){tmp=1;read(n),read(m);top=0;for(rg int i=1;i<=m;i++){int u,v;read(u),read(v);addb(u,v),addb(v,u);}DIE=0,dfs(1);if(DIE)ans=0;else{ans=1;for(rg int i=1;i<=n;i++)vis[i]=0;for(rg int i=1;i<=n;i++)if(!vis[i]){int d=0;for(rg int j=head[i];j;j=nxt[j])if(!del[j])d++;if(d==1){DFS(i,0);ans=ans*f[i]%mod;}}}print(ans),putchar('\n');for(rg int i=1;i<=n;i++)head[i]=0,vis[i]=0;}return flush(),0;
}
总结
其实和仙人掌没啥关系,就是很清真的树形dp,被题目名劝退了
然后转化问题的思路比较巧妙
最后,祈祷今年浙江省选RP++吧
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
