Description
定義線圖為把無向圖的邊變成點(diǎn),新圖中點(diǎn)與點(diǎn)之間右邊當(dāng)且僅當(dāng)它們對(duì)應(yīng)的邊在原圖中有公共點(diǎn),這樣得到的圖。
定義弦圖為不存在一個(gè)長(zhǎng)度大于3的純環(huán),純環(huán)的定義是在環(huán)上任取兩個(gè)不相鄰的點(diǎn),它們之間都沒有邊,也就是不存在沒有弦的環(huán)的無向圖。
現(xiàn)在給出一棵n個(gè)點(diǎn)的樹,你可以在上面添加任意多條邊(不能重邊),要求得到的圖的線圖是弦圖,求加邊的方案數(shù)。
n<=200000
Solution
畫圖可以發(fā)現(xiàn),一個(gè)無向圖的線圖是弦圖的充要條件就是不存在長(zhǎng)度大于3的環(huán)(不一定是純環(huán))
也就是說,我們加邊只能加成三角形,并且加的邊還不能交叉。
轉(zhuǎn)化后的題意等價(jià)于將樹上的邊分成若干個(gè)集合,每個(gè)集合要么是單獨(dú)一條邊,要么是兩條相鄰的邊,問方案數(shù)。
\(O(n^2)\)的暴力呼之欲出,我們記\(f[i][0/1]\)表示處理完\(i\)的子樹,\(i\)向父親的這條邊是否與子樹內(nèi)的邊組合了。
轉(zhuǎn)移的時(shí)候,我們只需要知道所有兒子中有幾個(gè)0,也就是有幾條邊需要我們來分配。
不妨再DP預(yù)處理出一個(gè)數(shù)組\(g[i][0/1]\),表示一個(gè)點(diǎn)下面掛著i條邊,要分配集合,i的父親邊是否參與的方案數(shù)。
我們考慮每次新加一條邊,要么自成集合,要么與之前的某一個(gè)組合,有
\(g[i][0]=g[i-1]+g[i-2][0]*(i-1),g[i][1]=g[i-1][0]*i\)
由于只要知道有幾個(gè)0,這顯然可以分治NTT優(yōu)化,這樣就做完了。
時(shí)間復(fù)雜度\(O(n\log^2 n)\)
Code
以下代碼未經(jīng)測(cè)試,完全不能保證正確性。
(慘遭證偽,請(qǐng)勿參考)
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 200005
#define M 524288
using namespace std;
const int mo=998244353;
typedef long long LL;
int n,m1,fs[N],nt[2*N],dt[2*N];
LL f[N][2],g[N];
LL ksm(LL k,LL n)
{LL s=1;for(;n;n>>=1,k=k*k%mo) if(n&1) s=s*k%mo;return s;
}
void link(int x,int y)
{nt[++m1]=fs[x];dt[fs[x]=m1]=y;
}
namespace poly
{int len,st[N],le[N],a[M+1];int u1[M+1],u2[M+1],l2[M+1],cf[20],wg[M+1],wi[M+1],ny[M+1],bit[M+1];void init(){fo(i,0,18) l2[cf[i]=(1<<i)]=i;fod(i,M-1,2) if(!l2[i]) l2[i]=l2[i+1];wg[0]=1,wg[1]=ksm(3,(mo-1)/M);ny[1]=1;fo(i,2,M) wg[i]=(LL)wg[i-1]*wg[1]%mo,ny[i]=(-(LL)ny[mo%i]*(mo/i)%mo+mo)%mo;}void prp(int num){int p=M/num,w=cf[l2[num]-1];fo(i,0,num-1) bit[i]=(bit[i>>1]>>1)|((i&1)<<w),wi[i]=wg[i*p];}int inc(int x,int y) {x+=y;return(x>=mo)?x-mo:x;}int dec(int x,int y) {x-=y;return(x<0)?x+mo:x;}void DFT(int *a,int num){fo(i,0,num-1) if(i<bit[i]) swap(a[i],a[bit[i]]);for(int h=1,l=num>>1;h<num;h<<=1,l>>=1){for(int j=0;j<num;j+=(h<<1)){int *x=a+j,*y=x+h,*w=wi,v;for(int i=0;i<h;i++,x++,y++,w+=l){v=(LL)(*x)*(*y)%mo;*y=dec(*x,v),*x=inc(*x,v);}}}}void IDFT(int *a,int num){DFT(a,num);fo(i,0,num-1) a[i]=(LL)a[i]*ny[num]%mo;reverse(a+1,a+num);}void doit(int l,int r){if(l==r) return; int mid=(l+r)>>1;doit(l,mid),doit(mid+1,r);int num=cf[l2[le[l]+le[mid+1]-1]];prp(num);memset(u1,0,4*num),memset(u2,0,4*num);fo(i,0,le[l]-1) u1[i]=a[st[l]+i];fo(i,0,le[mid+1]-1) u2[i]=a[st[mid]+i];DFT(u1,num),DFT(u2,num);fo(i,0,num-1) u1[i]=(LL)u1[i]*u2[i]%mo;IDFT(u1,num);le[l]+=le[mid+1]-1;fo(i,0,le[l]-1) a[st[l]+i]=u1[i];}
}
using namespace poly;void dfs(int k,int fa)
{for(int i=fs[k];i;i=nt[i]) {int p=dt[i];if(p!=fa) dfs(p,k);}len=0;int cnt=0;for(int i=fs[k];i;i=nt[i]){int p=dt[i];if(p!=fa){cnt++;a[st[cnt]=len++]=f[p][1];a[len++]=f[p][0];le[cnt]=2;}}if(!cnt) f[k][0]=1;else{doit(1,cnt);f[k][0]=f[k][1]=0;fo(i,0,le[1]-1){f[k][0]=(f[k][0]+(LL)a[i]*g[i])%mo;if(i) f[k][1]=(f[k][1]+(LL)a[i]*g[i-1]%mo*(LL)i)%mo;}}
}
int main()
{cin>>n;fo(i,1,n-1){int x,y;scanf("%d%d",&x,&y);link(x,y),link(y,x);}g[0]=1,g[1]=1;fo(i,2,n) g[i]=(g[i-1]+g[i-2]*(i-1))%mo;init();dfs(1,0);printf("%lld\n",f[1][0]);
}
轉(zhuǎn)載于:https://www.cnblogs.com/BAJimH/p/10945891.html
總結(jié)
以上是生活随笔為你收集整理的【PKUSC2019】线弦图【计数】【树形DP】【分治FFT】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。