4784: [Zjoi2017]仙人掌

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 312  Solved: 181
[Submit][Status][Discuss]

Description

如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌。所谓简单环即不经过
重复的结点的环。
现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得这张图中的边数太少了,所以她想要在图上连上
一些新的边。同时为了方便的存储这张无向图,图中的边数又不能太多。经过权衡,她想要加边后得到的图为一棵
仙人掌。不难发现合法的加边方案有很多,可怜想要知道总共有多少不同的加边方案。两个加边方案是不同的当且
仅当一个方案中存在一条另一个方案中没有的边。

Input

多组数据,第一行输入一个整数T表示数据组数。
每组数据第一行输入两个整数n,m,表示图中的点数与边数。
接下来m行,每行两个整数u,v(1≤u,v≤n,u!=v)表示图中的一条边。保证输入的图
联通且没有自环与重边
Sigma(n)<=5*10^5,m<=10^6,1<=m<=n*(n-1)/2

Output

对于每组数据,输出一个整数表示方案数,当然方案数可能很大,请对998244353取模后
输出。

Sample Input

2
3 2
1 2
1 3
5 4
1 2
2 3
2 4
1 5

Sample Output

2
8
对于第一组样例合法加边的方案有 {}, {(2,3)},共 2 种。

HINT

Source

ZJOI2017 DAY1的题目质量相当高啊,都是比较自然清新的思路加上非毒瘤的代码,做起来真是一种享受。

由于以前没有接触过仙人掌DP,所以这里要有一个清楚的认识。

首先我们知道环套树DP,就是基环外向树类型的题目,大致就是先找到基环,然后对每棵树DP,最后枚举将环上的每一条边断开,具体见BZOJ1040骑士。

现在我们来看仙人掌图。仙人掌图的定义是每条边最多在一个简单环中,仔细分析可知,实际上就是环和树的拼接,如果将所有环拿走的话会发现,整幅图会变成一个森林。

那么我们就可以从这个方向考虑这个问题了。第一个问题是,如何判断一个图是不是仙人掌图。首先建出DFS树,然后对于每条反祖边,将整个环上的边标记,如果有边被标记超过两次则说明不是仙人掌图。具体可以看下面的代码,这里还有一种方法:用树上差分实现标记。

 void _dfs(int x,int f) {
vi[x]=;
dep[x]=dep[f]+;
RAL(i,x) if(e[i].to!=f) {
if(!vi[e[i].to]) _dfs(e[i].to,x);
else if(dep[e[i].to]<dep[x]) {
bt[x]++;bt[e[i].to]--;
}
}
} int fl;
void _gatherS(int x) {
RAL(i,x) if(dep[x]+==dep[e[i].to]) {
_gatherS(e[i].to);bt[x]+=bt[e[i].to];
if(!bt[e[i].to]) bi[i]=bi[i^]=;
} if(bt[x]>) fl=;
}

现在考虑如何DP,首先我们知道我们不可能在环上加边,所以我们忽略掉环边,这题就成功转化为了树形DP。然后对于每棵树求出可以加边的方案数,这个就是常规的DP。具体可以看:

https://www.cnblogs.com/wfj2048/p/6636028.html

这样,问题就轻松解决了。思路非常清晰而巧妙,确实是一道好题。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=l; i<=r; i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=,md=;
struct D{ int id,d; }a[N];
int h[N],fa[N],dfn[N],lu[N],dep[N],to[N<<],nxt[N<<],n,m,u,v,cnt,T,nd;
ll f[N],g[N],ans;
bool cmp(const D &a,const D &b){ return a.d<b.d; } void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x,int p){
fa[x]=p; dfn[x]=++nd; dep[x]=dep[p]+;
For(i,x) if (!dfn[k=to[i]]) dfs(k,x);
} void dp(int x,int rt){
lu[x]=-; f[x]=; int tot=;
For(i,x) if ((k=to[i])!=fa[x] && lu[k]==) tot++,dp(k,),f[x]=f[x]*f[k]%md;
if (!rt) f[x]=f[x]*g[tot+]%md; else f[x]=f[x]*g[tot]%md;
} void work(){
scanf("%d%d",&n,&m); cnt=;
rep(i,,n) lu[i]=fa[i]=dep[i]=dfn[i]=h[i]=;
rep(i,,m) scanf("%d%d",&u,&v),add(u,v),add(v,u);
nd=; dfs(,);
rep(i,,m){
int u=to[i<<],v=to[(i<<)|];
if (dfn[u]<dfn[v]) swap(u,v);
while (u!=v){
if (lu[u]==){ printf("0\n"); return; }
lu[u]++; u=fa[u];
}
}
rep(i,,n) a[i].id=i,a[i].d=dep[i];
sort(a+,a+n+,cmp); ans=;
rep(i,,n){
int x=a[i].id; if (lu[x]==-) continue;
dp(x,); ans=ans*f[x]%md;
}
printf("%lld\n",ans);
} int main(){
g[]=g[]=; rep(i,,) g[i]=(g[i-]+(i-)*g[i-])%md;
for (scanf("%d",&T); T--; ) work();
return ;
}

[BZOJ4784][ZJOI2017]仙人掌(树形DP)的更多相关文章

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

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

  2. uoj#290. 【ZJOI2017】仙人掌(数数+仙人掌+树形dp)

    传送门 这图可以说是非常形象了2333 模拟赛的时候打了个表发现为一条链的时候答案是\(2^{n-2}\)竟然顺便过了第一个点 然后之后订正的时候强联通分量打错了调了一个上午 首先不难发现我们可以去掉 ...

  3. bzoj4784 [Zjoi2017]仙人掌

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

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

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

  5. 2019.02.07 bzoj4316: 小C的独立集(仙人掌+树形dp)

    传送门 题意:给出一个仙人掌森林求其最大独立集. 思路:如果没有环可以用经典的树形dpdpdp解决. fi,0/1f_{i,0/1}fi,0/1​表示第iii个点不选/选的最大独立集. 然后fi,0+ ...

  6. BZOJ 4316: 小C的独立集 仙人掌 + 树形DP

    4316: 小C的独立集 Time Limit: 10 Sec  Memory Limit: 128 MB Description 图论小王子小C经常虐菜,特别是在图论方面,经常把小D虐得很惨很惨. ...

  7. 2019.02.07 bzoj1487: [HNOI2009]无归岛(仙人掌+树形dp)

    传送门 人脑转化条件过后的题意简述:给你一个仙人掌求最大带权独立集. 思路:跟这题没啥变化好吗?再写一遍加深记忆吧. 就是把每个环提出来分别枚举环在图中的最高点选还是不选分别dpdpdp一下即可,时间 ...

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

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

  9. bzoj 4784: [Zjoi2017]仙人掌【tarjan+树形dp】

    其实挺简单的但是没想出来---- 首先判断无解情况,即,一开始的图就不是仙人掌,使用tarjan判断如果一个点dfs下去有超过一个点比他早,则说明存在非简单环. 然后考虑dp,显然原图中已经属于某个简 ...

随机推荐

  1. Vue 子路由 与 单页面多路由 的区别

    本文地址:http://www.cnblogs.com/veinyin/p/7911292.html 最近学完了基础课程,打算整理一波笔记,对基本概念梳理一遍,惊觉对子路由和单页面多路由混淆的一塌糊涂 ...

  2. 查询timestamp类型数据

    $where=" roleid = 8 and lizhi = 0 and branchid IN (".implode(",",$ids).") a ...

  3. 【leetcode 简单】第三十八题 两数之和 II - 输入有序数组

    给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数. 函数应该返回这两个下标值index1 和 index2,其中 index1 必须小于 index2. 说明: 返回的下标值( ...

  4. Vue 脱坑记

    问题汇总 Q:安装超时(install timeout) 方案有这么些: cnpm : 国内对npm的镜像版本 /* cnpm website: https://npm.taobao.org/ */ ...

  5. 仿360影视网站模板html

    链接:http://pan.baidu.com/s/1mhIkV4s 密码:9wgq

  6. OkHttp与Cookie及Cookie的持久化

    http://blog.csdn.net/u011150924/article/details/52780931 http://blog.csdn.net/chen19960724/article/d ...

  7. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  8. linux下route命令--说的比较清楚!

    linux下route命令     route命令感觉很不容易.一般开机后在命令行中使用route命令,会得到下面的信息   Kernel IP routing table   Destination ...

  9. ProxySQL 监控和统计

    ProxySQL 监控和统计 很多有价值的统计数据在stats和monitor库中. admin@127.0.0.1 [(none)]>SHOW TABLES FROM stats; +---- ...

  10. ASP.NET中的状态保持

    1.ASP.NET中的状态保持解决方案 2.常用的状态报保持方式 view state  ASP.Net的.aspx页面特有,页面级的,就是在页面上的一个隐藏域中保存客户端单独使用的数据的一种方式(b ...