LOJ#6496. 「雅禮集訓 2018 Day1」仙人掌 (圓方樹+分治NTT)

題面:https://loj.ac/problem/6496

 

 

 

題解

毒瘤的一道

先把仙人掌轉成圓方樹

我們考慮DP

設f[u][0/1/2]表示點u如果向上方連0/1/2條出邊,以u爲根的子樹的所有邊的定向的方案數

如果一個點u是圓點

那麼點u的答案f[u][0/1/2]就是滿足(u的所有兒子貢獻的出度和<=a[u]-(0/1/2))的所有方案數

這種轉移可以寫成卷積的形式,而且每個多項式的最高次冪不超過2,可以進行分治NTT優化

(這裏的方點(原圖的環)就可以看作可以貢獻兩個出度的兒子)

如果一個點u是方點

那麼方點的父親就是該環的頂部

我們枚舉頂部左端的第一條邊的方向(指向頂部或不指向頂部)

那麼一個環上的方案就可以遞推出來了

紅色的邊爲已經定向的邊,綠色的邊表示我們正在枚舉計算的邊

設g0,g1表示當前點v被前一個點(指向/不指向)的方案數

t0,t1表示下一個點被v(指向/不指向)的方案數

那麼顯然有

t0=g0*f[v][1]+g1*f[v][2]

t1=g0*f[v][0]+g1*f[v][1]

(因爲一個點v最多隻有一個方父親,相當於v可以向u連兩個出邊)

但是當前的方點u的父親會由於我們的定向不同而向下連不同條出邊

所以我們一開始要單獨枚舉定向,分別算出不同定向方案對f[u][0/1/2]的貢獻

(其實這裏的方點的f[u][0/1/2]就表示環u可以向環頂連0/1/2條出邊的方案數)

代碼:(這題會有重邊)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 400005
int fir[N],to[2*N],nxt[2*N],d[N],cnt;
void adde(int a,int b)
{
	to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;
}
int dfn[N],low[N],stk[N],top,dc;
vector<int> G[N],id[N];bool vis[N];
int val[N],tot;
void dfs(int u)
{
	dfn[u]=low[u]=++dc;
	stk[++top]=u;
	for(int v,i=0;i<(int)G[u].size();i++){
		if(vis[id[u][i]])continue;
		vis[id[u][i]]=1;
		if(!dfn[v=G[u][i]]){
			dfs(v);
			low[u]=min(low[u],low[v]);
			if(low[v]==dfn[u]){
				adde(u,++tot);int x;
				do{x=stk[top--];adde(tot,x);}while(x!=v);
			}
			else if(low[v]>dfn[u])adde(u,v),top--;	
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
#define P vector<int>
const int mod=998244353;
int wn[N<<1],wl;
int f[N][3];
int ksm(int x,int y)
{
	int ret=1;
	while(y){
		if(y&1)ret=1ll*ret*x%mod;
		y>>=1;x=1ll*x*x%mod;
	}
	return ret;
}
void init(int n)
{
	wl=wn[0]=1;
	while(wl<=n)wl<<=1;
	wn[1]=ksm(3,(mod-1)/wl);
	for(int i=2;i<=wl;i++)wn[i]=1ll*wn[i-1]*wn[1]%mod;
}
int rev[N];
void NTT(P &a,int len,int flg)
{
	int i,j,k,x,y;
	for(i=0;i<len;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
	for(i=1,x=(wl>>1);i<len;i<<=1,x>>=1){
		for(j=0;j<len;j+=(i<<1)){
			for(k=j,y=0;k<i+j;k++,y+=x){
				int tmp=1ll*a[i+k]*wn[flg==1?y:wl-y]%mod;
				a[i+k]=(a[k]+mod-tmp)%mod;
				a[k]=(a[k]+tmp)%mod;
			}
		}
	}
	if(flg==-1)for(i=0,x=ksm(len,mod-2);i<len;i++)
		a[i]=1ll*a[i]*x%mod;
}
P mul(P a,P b)
{
	int ret=a.size()+b.size()-1,len=1;
	while(len<ret)len<<=1;a.resize(len);b.resize(len);
	for(int i=0;i<len;i++)rev[i]=(rev[i>>1]>>1)|(i&1?len>>1:0);
	NTT(a,len,1);NTT(b,len,1);
	for(int i=0;i<len;i++)a[i]=1ll*a[i]*b[i]%mod;
	NTT(a,len,-1);a.resize(ret);
	return a;
}

P tmp[N];
int n,m,tcnt;
P solve(int l,int r)
{
	if(l==r)return tmp[l];
	int mid=(l+r)>>1;
	return mul(solve(l,mid),solve(mid+1,r));
}
void DP(int u)
{
	for(int v,p=fir[u];p;p=nxt[p])DP(to[p]);
	if(u<=n){
		if(!fir[u]){
			f[u][0]=1;f[u][1]=(val[u]>=1);f[u][2]=(val[u]>=2);
			return;
		}
		tcnt=0;
		for(int v,p=fir[u];p;p=nxt[p]){
			v=to[p];
			tmp[++tcnt].clear();
			if(v>n)tmp[tcnt].push_back(f[v][2]);
			tmp[tcnt].push_back(f[v][1]);
			tmp[tcnt].push_back(f[v][0]);
		}
		P ret=solve(1,tcnt);
		ret.resize(val[u]+1);
		for(int i=1;i<=val[u];i++)
			ret[i]=(ret[i]+ret[i-1])%mod;
		f[u][0]=ret[val[u]];
		if(val[u]>=1)f[u][1]=ret[val[u]-1];
		if(val[u]>=2)f[u][2]=ret[val[u]-2];
	}
	else{
		//f[u][0/1/2]
		int g0,g1,t0,t1;
		for(int k=0;k<=1;k++){
			g0=!k;g1=k;
			for(int v,p=fir[u];p;p=nxt[p]){
				v=to[p];
				t0=(1ll*g0*f[v][1]+1ll*g1*f[v][2])%mod;
				t1=(1ll*g0*f[v][0]+1ll*g1*f[v][1])%mod;
				g0=t0;g1=t1;
			}
			f[u][k+1]=(f[u][k+1]+g0)%mod;
			f[u][k]=(f[u][k]+g1)%mod;
		}
	}
}
int main()
{
	int i,u,v;
	n=gi();m=gi();
	init(2*n);
	for(i=1;i<=m;i++){
		u=gi();v=gi();
		G[u].push_back(v);id[u].push_back(i);
		G[v].push_back(u);id[v].push_back(i);
		d[u]++;d[v]++;
	}
	for(i=1;i<=n;i++)val[i]=min(gi(),d[i]);
	tot=n;for(i=1;i<=n;i++)if(!dfn[i])dfs(i);
	DP(1);
	printf("%d\n",f[1][0]);
}

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章