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

 

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树,然后对于每条反祖边,将整个环上的边标记,如果有边被标记超过两次则说明不是仙人掌图。具体可以看下面的代码,这里还有一种方法:用树上差分实现标记。

 1 void _dfs(int x,int f) {
 2     vi[x]=1;
 3     dep[x]=dep[f]+1;
 4     RAL(i,x) if(e[i].to!=f) {
 5         if(!vi[e[i].to]) _dfs(e[i].to,x);
 6         else if(dep[e[i].to]<dep[x]) {
 7             bt[x]++;bt[e[i].to]--;
 8         }
 9     }
10 }
11  
12 int fl;
13 void _gatherS(int x) {
14     RAL(i,x) if(dep[x]+1==dep[e[i].to]) {
15         _gatherS(e[i].to);bt[x]+=bt[e[i].to];
16         if(!bt[e[i].to]) bi[i]=bi[i^1]=1;
17     } if(bt[x]>1) fl=0;
18 }

 

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

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

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

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #define rep(i,l,r) for (int i=l; i<=r; i++)
 5 #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
 6 typedef long long ll;
 7 using namespace std;
 8 
 9 const int N=500100,md=998244353;
10 struct D{ int id,d; }a[N];
11 int h[N],fa[N],dfn[N],lu[N],dep[N],to[N<<2],nxt[N<<2],n,m,u,v,cnt,T,nd;
12 ll f[N],g[N],ans;
13 bool cmp(const D &a,const D &b){ return a.d<b.d; }
14 
15 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
16 
17 void dfs(int x,int p){
18     fa[x]=p; dfn[x]=++nd; dep[x]=dep[p]+1;
19     For(i,x) if (!dfn[k=to[i]]) dfs(k,x);
20 }
21 
22 void dp(int x,int rt){
23     lu[x]=-1; f[x]=1; int tot=0;
24     For(i,x) if ((k=to[i])!=fa[x] && lu[k]==1) tot++,dp(k,0),f[x]=f[x]*f[k]%md;
25     if (!rt) f[x]=f[x]*g[tot+1]%md; else f[x]=f[x]*g[tot]%md;
26 }
27 
28 void work(){
29     scanf("%d%d",&n,&m); cnt=1;
30     rep(i,1,n) lu[i]=fa[i]=dep[i]=dfn[i]=h[i]=0;
31     rep(i,1,m) scanf("%d%d",&u,&v),add(u,v),add(v,u);
32     nd=0; dfs(1,0);
33     rep(i,1,m){
34         int u=to[i<<1],v=to[(i<<1)|1];
35         if (dfn[u]<dfn[v]) swap(u,v);
36         while (u!=v){
37             if (lu[u]==2){ printf("0\n"); return; }
38             lu[u]++; u=fa[u];
39         }
40     }
41     rep(i,1,n) a[i].id=i,a[i].d=dep[i];
42     sort(a+1,a+n+1,cmp); ans=1;
43     rep(i,1,n){
44         int x=a[i].id; if (lu[x]==-1) continue;
45         dp(x,1); ans=ans*f[x]%md;
46     }
47     printf("%lld\n",ans);
48 }
49 
50 int main(){
51     g[0]=g[1]=1; rep(i,2,500000) g[i]=(g[i-1]+(i-1)*g[i-2])%md;
52     for (scanf("%d",&T); T--; ) work();
53     return 0;
54 }

 

转载于:https://www.cnblogs.com/HocRiser/p/8541703.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值