1. 程式人生 > >[BZOJ4784][ZJOI2017]仙人掌(樹形DP)

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

target img 結點 sam 連通 line ems 有環 inpu

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 }

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