【ZJOI2017】仙人掌





参考博客:https://www.cnblogs.com/wfj2048/p/6636028.html

我们先求出\(dfs\)树(就是\(dfs\)一遍),然后问题就变成了树形\(DP\)。

我们先判断无解:就用定义来判断,如果一条边出现在多个环里面就无解。

然后我们将所有在环上的边拆了,因为这些边不可能再出现在一个新的环中。于是我们得到了一个森林。

我们设\(f_v\)表示以\(v\)为根的子树得到仙人掌的方案数。\(ans=\prod_{v\ is\ root} f_v\)。

首先我们考虑子树之间不连边,容易得到:\(f_v=\prod f_{son_v}\),也就是所有子树\(f\)之积。

然后我们观察子树之间的连边:一个子树最多只会连出去一条边,也就是只会两两配对连边。我们设\(g_v\)表示有\(v\)个子树两两之间连边的方案数,则:

\[g_v=g_{v-1}+(v-1)*g_{v-2}
\]

这个转移的意义是考虑第\(v\)个点连不连边,如果不连,方案数就是其他\(v-1\)个点连边的方案;如果连,就从之前的\(v-1\)个点中任选一个相连,剩下的再连边。

设\(|sn_v|\)表示\(v\)的儿子个数。则\(f_v=g_{|sn_v|}*\prod f_{son_v}\)。

我的理解是:假设\(v\)的两个儿子\(a,b\)的子树之间要连边,设\(e_a,e_b\)分别表示\(a\)到\(v\)和\(b\)到\(v\)的边,那么我们一定是将之前覆盖了\(e_a\)和\(e_b\)的两个环的起点(深度最深的那个点,如果没有,就是\(a,b\))连接在一起。所以答案是\(g_{|sn_v|}\)乘上所有子树中连边的方案数。

但是\(v\)的子树中还可以向\(v\)的父亲连边。我们就把\(v\)的父亲也看做一个子节点。所以:

\[\begin{cases}
f_v=g_{|sn_v|}*\prod f_{sn_v}\ (v\ is\ not\ root)\\
f_v=g_{|sn_v|+1}*\prod f_{sn_v}\ (v\ is\ root)
\end{cases}
\]

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 1000005 using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} const ll mod=998244353;
int n,m;
int x[N],y[N];
struct graph {
int cnt,to[N<<2],nxt[N<<2];
int h[N<<1];
void add(int i,int j) {
to[++cnt]=j;
nxt[cnt]=h[i];
h[i]=cnt;
}
void Init() {
cnt=0;
for(int i=1;i<=n;i++) h[i]=0;
}
}s; ll f[N],g[N];
int dfn[N],id;
int dep[N],fa[N];
int tim[N];
void dfs(int v) {
dfn[v]=++id;
for(int i=s.h[v];i;i=s.nxt[i]) {
int to=s.to[i];
if(dfn[to]) continue ;
fa[to]=v;
dep[to]=dep[v]+1;
dfs(to);
}
} bool cmp(int a,int b) {return dep[a]<dep[b];} bool vis[N];
void DP(int v,int flag) {
vis[v]=1;
f[v]=1;
int sn=0;
for(int i=s.h[v];i;i=s.nxt[i]) {
int to=s.to[i];
if(tim[to]>1) continue ;
if(to==fa[v]||fa[to]!=v) continue ;
sn++;
DP(to,0);
f[v]=f[v]*f[to]%mod;
}
if(flag) f[v]=f[v]*g[sn]%mod;
else f[v]=f[v]*g[sn+1]%mod;
} int st[N];
void work() {
for(int i=1;i<=m;i++) {
int a=x[i],b=y[i];
if(dfn[a]<dfn[b]) swap(a,b);
while(a!=b) {
tim[a]++;
if(tim[a]>2) {
cout<<0<<"\n";
return ;
}
a=fa[a];
}
}
for(int i=1;i<=n;i++) st[i]=i;
sort(st+1,st+1+n,cmp);
ll ans=1;
for(int i=1;i<=n;i++) {
int now=st[i];
if(vis[now]) continue ;
DP(now,1);
ans=ans*f[now]%mod;
}
cout<<ans<<"\n";
} void Init() {
s.Init();
for(int i=1;i<=n;i++) vis[i]=tim[i]=fa[i]=dfn[i]=dep[i]=0;
id=0;
} int main() {
g[0]=g[1]=1;
for(int i=2;i<=500005;i++) g[i]=(g[i-1]+g[i-2]*(i-1))%mod;
int T=Get();
while(T--) {
n=Get(),m=Get();
Init();
for(int i=1;i<=m;i++) {
x[i]=Get(),y[i]=Get();
s.add(x[i],y[i]),s.add(y[i],x[i]);
}
dep[1]=1;
dfs(1);
work();
}
return 0;
}

【ZJOI2017】仙人掌的更多相关文章

  1. 【BZOJ4784】[ZJOI2017]仙人掌(Tarjan,动态规划)

    [BZOJ4784][ZJOI2017]仙人掌(Tarjan,动态规划) 题面 BZOJ 洛谷 题解 显然如果原图不是仙人掌就无解. 如果原图是仙人掌,显然就是把环上的边给去掉,变成若干森林连边成为仙 ...

  2. [BZOJ4784][ZJOI2017]仙人掌(树形DP)

    4784: [Zjoi2017]仙人掌 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 312  Solved: 181[Submit][Status] ...

  3. bzoj4784 [Zjoi2017]仙人掌

    Description 如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌.所谓简单环即不经过重复的结点的环. 现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得 ...

  4. ●洛谷P3687 [ZJOI2017]仙人掌

    题链: https://www.luogu.org/problemnew/show/P3687题解: 计数DP,树形DP. (首先对于这个图来说,如果初始就不是仙人掌,那么就直接输出0) 然后由于本来 ...

  5. 【做题】ZJOI2017仙人掌——组合计数

    原文链接 https://www.cnblogs.com/cly-none/p/ZJOI2017cactus.html 给出一个\(n\)个点\(m\)条边的无向连通图,求有多少种加边方案,使得加完后 ...

  6. LOJ2250 [ZJOI2017] 仙人掌【树形DP】【DFS树】

    题目分析: 不难注意到仙人掌边可以删掉.在森林中考虑树形DP. 题目中说边不能重复,但我们可以在结束后没覆盖的边覆盖一个重复边,不改变方案数. 接着将所有的边接到当前点,然后每两个方案可以任意拼接.然 ...

  7. zjoi2017 仙人掌

    题解: 好难的dp啊...看题解看了好久才看懂 http://blog.csdn.net/akak__ii/article/details/65935711 如果一开始的图就不是仙人掌,答案显然为0, ...

  8. 2019.02.07 bzoj4784: [Zjoi2017]仙人掌(仙人掌+树形dp)

    传送门 题意:给一个无向连通图,问给它加边形成仙人掌的方案数. 思路: 先考虑给一棵树加边形成仙人掌的方案数. 这个显然可以做树形dp. fif_ifi​表示把iii为根的子树加边形成仙人掌的方案数. ...

  9. BZOJ4784 ZJOI2017仙人掌(树形dp+dfs树)

    首先考虑是棵树的话怎么做.可以发现相当于在树上选择一些长度>=2的路径使其没有交,同时也就相当于用一些没有交的路径覆盖整棵树. 那么设f[i]为覆盖i子树的方案数.转移时考虑包含根的路径.注意到 ...

  10. 【题解】ZJOI2017仙人掌

    感觉这题很厉害啊,虽然想了一天多但还是失败了……(:д:) 这题首先注意到给定图中如果存在环其实对于答案是没有影响的.然后关键之处就在于两个 \(dp\) 数组,其中 \(f[u]\) 表示以 \(u ...

随机推荐

  1. 在SoapUI中模拟用户操作

    SoapUI作为一款接口测试工具,具有极大的灵活性和拓展性.它可以通过安装插件,拓展其功能.Selenium作为一款Web自动化测试插件可以很好的与SoapUI进行集成.如果要在SoapUI中模拟用户 ...

  2. PHP之单例模式

    之前记得有写过PHP的几种这模式.这几天看群里在问单列模式,觉得还是有必要再深入写清楚下..其实单例模式很好理解滴哦 单例模式顾名思义,就是只有一个实例,作为对象的创建模式,单例模式确保某一个类只有一 ...

  3. js对HTML字符转义与反转义

    注意: 在编写html时,经常需要转义,才能正常显示在页面上. 并且,还可以防止xss. 解决方案: 一, 使用正则: 使用正则转码: var value = document.getElementB ...

  4. hive函数应用之操作json

    1.创建表 createtable.sql中存放的创建表语句如下 create external table adt.jsontest ( appKey string comment "AP ...

  5. nginx比apache处理静态文件速度快,但是nginx处理大量并发的php请求时,容易出现502错误,频率大概是多少

    首先要明确一点的是502是怎么出现的,为什么会出现502呢? 一般而言,出现502的错误是因为php-cgi连接数不够导致的.举个例子:php-cgi开10个进程,前端发20个请求,每个请求的脚本都s ...

  6. angular ng-repeat 动态获取的dom片段 显示

    .filter('to_trusted', ['$sce',function ($sce) { return function (text) { return $sce.trustAsHtml(tex ...

  7. JavaScript for/in 语句 遍历数组内容

    for-in遍历 for-in是为遍历对象而设计的,不适用于遍历数组. 遍历数组的缺点:数组的下标index值是数字,for-in遍历的index值"0","1" ...

  8. jfinal框架学习过程

    刚刚学习jfinal,通过一天左右的时间大体上理解了这个框架的用法,我对他的理解是JFinal 是基于 Java 语言的极速 WEB + ORM 框架,其核心设计目标是开发迅速.代码量少.学习简单.功 ...

  9. FreeSWITCH1.6安装教程Centos

    介绍 FreeSWITCH是个网络电话转化器,相当与网络转换器,不过这个是用于IP电话,构建公司的电话通讯系统需要用该技术,因为按照官方文档无法成功安装. 本文目的:FreeSWITCH安装步骤 官方 ...

  10. virtualbbox centos7 NAT模式外网 Host-only Adapter模式联网 双网卡

    1.下载oracle VM virtualbox  centos7 1.1. 下载地址:https://www.virtualbox.org/wiki/Downloads https://www.ce ...