bzoj 4784: [Zjoi2017]仙人掌 树形dp+双连通分量
2017-03-29 19:23
387 查看
题意
给出一个仙人掌,无重边自环,问有多少种加边方案使得其还是一个仙人掌(可以不加)。n<=500000,m<=1000000
分析
显然一开始可以特判掉不是仙人掌的情况,然后输出0.这个可以用树上差分来实现。然后将所有的环都找出来,将环上的边标记为不可走,那么剩下的边就组成了一个森林,我们就可以愉快的树形dp啦!
考虑一棵树,我们设强制每颗子树必然要加一条连到其祖先的边(根节点特判),显然这样的边只能有一条。若本来没有则相当于子树的根节点朝其父亲又连了一条边。那么问题就转化成了有多少种方案使得所有的边都被覆盖一次。
设f[x]表示以x为根的子树组成了仙人掌且有一条边连向祖先有多少种加边方案。
再设g[x]表示x个点任意匹配的方案数,显然g[x]=g[x-1]+g[x-2]*(x-1)
那么有两种情况
一种是x的子树内没有边连向祖先,也就是x连了一条边到祖先
那么对f[x]的贡献就是(∏f[son])∗g[size],size表示x的儿子数量
简单解释一下,因为x的每颗子树都连了一条边出来,我们可以将两颗子树连出来的边连到一起,也可以将某棵子树的边连到x上,那么就相当于size个节点任意匹配的方案数了。
一种是x的某棵子树内的边连到了其祖先
那么对f[x]的贡献就是(∏f[son])∗g[size−1]∗size
因为假设y的子树连了一条到x的祖先的边,那么其贡献就是f[y]∗(∏son!=yf[son])∗g[size−1]
就等于(∏f[son])∗g[size−1],又因为有size种选择,所以就乘上一个size。
那么这道题至此就被解决啦!!!
ps:据说在考场上A了这道题再水一点暴力分可以排到浙江省的前十哟
本来打起来是很爽的,但由于本沙茶对求边双不熟悉而且不太清楚怎么特判非仙人掌情况,所以就码了一个下午才A掉。。。
代码
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define N 500005 #define LL long long #define MOD 998244353 using namespace std; int n,m,g ,f ,last ,dfn ,low ,cnt,lu ,tim,top,sta ,flag=0; bool ins ; struct edge{int to,next,use;}e[N*2]; int read() { int x=0,f=1;char ch=getchar(); while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void prework() { g[0]=g[1]=1;g[2]=2; for (int i=3;i<=500000;i++) g[i]=(g[i-1]+(LL)g[i-2]*(i-1)%MOD)%MOD; } void addedge(int u,int v) { e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;e[cnt].use=0; e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;e[cnt].use=0; } void dfs1(int x) { dfn[x]=1;ins[x]=1; for (int i=last[x];i;i=e[i].next) if (!dfn[e[i].to]) dfs1(e[i].to); else if (ins[e[i].to]) lu[x]++,lu[e[i].to]--; ins[x]=0; } bool dfs2(int x) { dfn[x]=1; for (int i=last[x];i;i=e[i].next) if (!dfn[e[i].to]) { if (!dfs2(e[i].to)) return 0; lu[x]+=lu[e[i].to]; } if (lu[x]>2) return 0; else return 1; } bool check() { for (int i=1;i<=n;i++) dfn[i]=lu[i]=0; dfs1(1); for (int i=1;i<=n;i++) dfn[i]=0; return dfs2(1); } void tarjan(int x,int fa) { dfn[x]=low[x]=++tim; ins[x]=1; for (int i=last[x];i;i=e[i].next) { if (e[i].to==fa) continue; if (!dfn[e[i].to]) { sta[++top]=i; tarjan(e[i].to,x); low[x]=min(low[x],low[e[i].to]); if (low[e[i].to]==dfn[x]) { int y=0; while (y!=i) { y=sta[top--];e[y].use=e[y^1].use=1; } } } else if (ins[e[i].to]) low[x]=min(low[x],dfn[e[i].to]),sta[++top]=i; } ins[x]=0; if (dfn[x]==low[x]&&top) top--; } void dp(int x,int root) { dfn[x]=1;f[x]=1;int w=1,size=0; for (int i=last[x];i;i=e[i].next) { if (dfn[e[i].to]||e[i].use) continue; dp(e[i].to,root); size++;w=(LL)w*f[e[i].to]%MOD; } if (root==x) f[x]=(LL)w*g[size]%MOD; else f[x]=(LL)w*g[size+1]%MOD; } int main() { freopen("cactus.in","r",stdin);//freopen("cactus.out","w",stdout); prework(); int T=read(); while (T--) { n=read();m=read();cnt=1; for (int i=1;i<=n;i++) last[i]=0; for (int i=1;i<=m;i++) { int x=read(),y=read(); addedge(x,y); } if (!check()) { printf("0\n"); continue; } for (int i=1;i<=n;i++) dfn[i]=low[i]=0; tim=0; tarjan(1,0); int ans=1; for (int i=1;i<=n;i++) dfn[i]=0; for (int i=1;i<=n;i++) if (!dfn[i]) dp(i,i),ans=(LL)ans*f[i]%MOD; printf("%d\n",ans); } return 0; }
相关文章推荐
- bzoj 4784: [Zjoi2017]仙人掌【tarjan+树形dp】
- [BZOJ4784][ZJOI2017]仙人掌(树形DP)
- bzoj4784 [Zjoi2017]仙人掌
- BZOJ4784: [Zjoi2017]仙人掌
- BZOJ4784 [Zjoi2017]仙人掌
- ZJOI2017 仙人掌 转化模型后的简单树形dp
- 4784: [Zjoi2017]仙人掌
- BZOJ4787/UOJ290 【ZJOI2017】仙人掌
- LOJ2250 [ZJOI2017] 仙人掌【树形DP】【DFS树】
- [BZOJ4784][UOJ290][ZJOI017]仙人掌-树形DP
- BZOJ 1060 ZJOI2007 时态同步 树形DP
- BZOJ 1060 ZJOI 2007 时态同步 树形DP
- 【ZJOI2017 Round1练习&BZOJ5354】D7T3 room(DP)
- BZOJ4890 [Tjoi2017]城市 【树形dp】
- bzoj4871: [Shoi2017]摧毁“树状图” //树形dp
- bzoj 1040 [ZJOI2008]骑士(基环外向树,树形DP)
- [线段树 瞎搞] BZOJ 4876 [Zjoi2017]线段树
- 【ZJOI2017】仙人掌
- 【BZOJ 4455】 4455: [Zjoi2016]小星星 (容斥原理+树形DP)
- [树形DP] BZOJ 4824 [Cqoi2017]老C的键盘