[zjoi2017]仙人掌
前言
謹以此題紀念我第一次參加省選時剛了5h這一題得到0分的經歷
題目相關
鏈接
題目大意
給出仙人掌定義:如果一個無自環無重邊無向連通圖的任意一條邊最多屬于一個簡單環,我們就稱之為仙人掌
給出一個圖,求有多少種加邊方式使得圖成為一個仙人掌
數據范圍
多組數據
∑n≤5?105,∑m≤106\sum n\le5*10^5,\sum m\le10^6∑n≤5?105,∑m≤106
題解
首先,如果這幅圖本身就已經不是仙人掌了,那么輸出000
如何判斷呢?我們發現,建圓方樹的時候,兩條圓點的連邊最多只會被刪除一次,如果被刪除了多次那么說明這幅圖不是仙人掌
對于仙人掌的簡單環上的邊,我們一定不可以進行一條連邊使得兩個端點之間有一條路徑經過這條邊(因為這樣就會出現一條邊在多個簡單環中的情況)
那么我們就直接把仙人掌簡單環上的邊直接刪除,容易發現剩下的都是一些樹了
對于樹的情況,我們發現有兩個東西不好處理:一個是最終的圖中可以有邊不在簡單環上,二是添加的邊不能有重邊
我們發現這兩個問題合在一起就被解決了,我們可以轉化成:每條邊都必須要在一個簡單環上,允許有重邊。容易發現,這么轉化的答案不變
現在相當于是對于一棵樹,求其被鏈覆蓋的方案數
考慮樹形dp
我們定義fif_ifi?為以iii節點所在子樹及其通往父親的邊被鏈覆蓋的方案數
設sonuson_usonu?為uuu節點的兒子集合,S(n)S(n)S(n)為nnn個點、每個點的度數小于等于111的圖的數量
我們發現fu=S(∣sonu∣+1)?∏v∈sonufvf_u=S(|son_u|+1)*\prod_{v\in son_u}f_vfu?=S(∣sonu?∣+1)?v∈sonu?∏?fv?
特殊的,我們發現根節點沒有父親邊,這樣的話其fif_ifi?的定義不包含通往父親的邊,那么S(x)S(x)S(x)里面的xxx就是兒子數量了
我們現在考慮怎么求S(n)S(n)S(n)
我們考慮已知S(1)S(1)S(1)~S(n)S(n)S(n),怎么求S(n+1)S(n+1)S(n+1)
我們考慮第n+1n+1n+1個點
如果它不連邊,那么方案數為S(n)S(n)S(n)
如果它連邊,在前面nnn個點中選擇一個連,那么方案數就為S(n?1)?nS(n-1)*nS(n?1)?n
即得到遞推式S(n+1)=S(n)+n?S(n?1)S(n+1)=S(n)+n*S(n-1)S(n+1)=S(n)+n?S(n?1)
這樣,我們就能O(n)\mathcal O(n)O(n)的完成此題
代碼
#include<cstdio> #include<cctype> #include<algorithm> #include<cstring> namespace fast_IO {const int IN_LEN=1000000,OUT_LEN=1000000;char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}inline void flush(){fwrite(obuf,1,oh-obuf,stdout);} } using namespace fast_IO; #define getchar() getchar_() #define putchar(x) putchar_((x)) #define rg register typedef long long ll; template<typename T>inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;} template<typename T>inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');} template<typename T>inline void print(const T x){if(x>=0)printe(x);else putchar('-'),printe(-x);} template <typename T> inline T min(const T a,const T b){return a<b?a:b;} template <typename T> inline T abs(const T a){return a>0?a:-a;} const int maxn=500005,maxm=2000005,mod=998244353; int T,n,m; int head[maxn],nxt[maxm],tow[maxm],tmp;bool del[maxm],DIE,VVV[maxm],vis[maxn]; inline void addb(const int u,const int v) {tmp++;nxt[tmp]=head[u];head[u]=tmp;tow[tmp]=v;del[tmp]=VVV[tmp>>1]=0; } int stack[maxn],top,H; void dfs(const int u) {if(DIE)return;vis[u]=1;for(rg int i=head[u];i&&!DIE;i=nxt[i])if(!VVV[i>>1]){VVV[i>>1]=1;const int v=tow[i];stack[++top]=i;if(vis[v]){H=top;for(rg int j=top-1;j&&tow[stack[j]]!=v;j--)H=j;for(rg int j=top;j>=H;j--){const int id=stack[j];if(del[id])DIE=1;del[id]=del[id^1]=1;}}else dfs(v);top--;} } ll S[maxn],f[maxn],ans; void DFS(const int u,const int fa) {int size=0;f[u]=1,vis[u]=1;for(rg int i=head[u];i;i=nxt[i])if(!del[i]){const int v=tow[i];if(v!=fa)DFS(v,u),f[u]=f[u]*f[v]%mod,size++;}if(fa)size++;f[u]=f[u]*S[size]%mod; } int main() {S[0]=S[1]=1;for(rg int i=2;i<=500000;i++)S[i]=(S[i-1]+S[i-2]*(i-1))%mod;read(T);while(T--){tmp=1;read(n),read(m);top=0;for(rg int i=1;i<=m;i++){int u,v;read(u),read(v);addb(u,v),addb(v,u);}DIE=0,dfs(1);if(DIE)ans=0;else{ans=1;for(rg int i=1;i<=n;i++)vis[i]=0;for(rg int i=1;i<=n;i++)if(!vis[i]){int d=0;for(rg int j=head[i];j;j=nxt[j])if(!del[j])d++;if(d==1){DFS(i,0);ans=ans*f[i]%mod;}}}print(ans),putchar('\n');for(rg int i=1;i<=n;i++)head[i]=0,vis[i]=0;}return flush(),0; }總結
其實和仙人掌沒啥關系,就是很清真的樹形dp,被題目名勸退了
然后轉化問題的思路比較巧妙
最后,祈禱今年浙江省選RP++吧
總結
以上是生活随笔為你收集整理的[zjoi2017]仙人掌的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [luogu4128][shoi2006
- 下一篇: 动态dp