LOJ#2496. 「AHOI / HNOI2018」毒瘤【樹加額外邊求獨立集方案數(枚舉狀態,虛樹優化轉移)】

題目描述:

如題,給出一棵樹以及額外的k11k\le 11條邊,求選出一個獨立集的方案數。n100000n\le100000

題目分析:

如果k=0k=0,那麼直接設f[u][0/1]f[u][0/1]表示uu選或不選DP。

現在k>0k>0,直接DP得出的方案可能有些不滿足限制。但是kk很小,k11k\le11條邊連接着22\le22個特殊點。

如果暴力枚舉這2222個特殊點的選法(強制其選或不選),再進行O(n)O(n)DP,可以得到O(222n)O(2^{22}n)的做法。

實際上不需要2222^{22},每條邊最多隻有3個狀態,可以壓縮爲3113^{11},進一步地,第一輪強制uuvv不選,第二輪強制uu不選,可以壓縮爲2112^{11}。於是就有了O(211n)O(2^{11}n)的做法。(常數小的選手可能可以大力rush過掉,比如這份 submission

每次只有22個點的狀態發生變化,但每次都要O(n)O(n)的DP,顯然有些浪費。
考慮這2222個點把樹分成了若干連通塊,如果每個連通塊只受一或兩個特殊點影響,或許可以用一些預處理轉移係數的方法。

使用虛樹,每個連通塊要麼對應虛樹上的一條邊,要麼是在虛樹某個葉節點的子樹中。

對於非虛樹邊上的貢獻可以預處理,而虛樹上的轉移邊可以寫作f[u][0/1] =af[v][0]+bf[v][1]f[u][0/1]~*=a*f[v][0]+b*f[v][1]的形式,a,ba,b是轉移係數,同樣可以預處理。

於是複雜度就是O(n+2kk)O(n+2^{k}*k)

Code:

#include<bits/stdc++.h>
#define maxn 100015
using namespace std;
const int mod = 998244353;
int n,m,cnt,ans,siz[maxn],dfn[maxn],tim;//siz means number of key points in different subtrees.
bool key[maxn],vis[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
struct edge{int x,y;}e[12];
void dfs(int u,int ff){
	dfn[u]=++tim;
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff){
		if(!dfn[v]) dfs(v,u),siz[u]+=siz[v];
		else if(dfn[v]<dfn[u]) key[u]=key[v]=1,e[++cnt]=(edge){u,v};
	}
	key[u]|=siz[u]>=2,siz[u]=siz[u]||key[u];
}
#define fi first
#define se second
typedef pair<int,int> pii;
struct node{int v; pii k0,k1;};
vector<node>G[maxn];
int f[maxn][2],F[maxn][2],fix[maxn][2];
pii k[maxn][2];
pii operator + (const pii &a,const pii &b){return pii((a.fi+b.fi)%mod,(a.se+b.se)%mod);}
pii operator * (const pii &a,const int &t){return pii(1ll*a.fi*t%mod,1ll*a.se*t%mod);}
int build(int u){
	f[u][0]=f[u][1]=1,vis[u]=1;
	int son=0;
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]){
		int now=build(v);
		if(!now) f[u][0]=1ll*f[u][0]*(f[v][0]+f[v][1])%mod,f[u][1]=1ll*f[u][1]*f[v][0]%mod;
		else if(!key[u]) k[u][0]=k[v][0]+k[v][1],k[u][1]=k[v][0],son=now;
		else G[u].push_back((node){now,k[v][0]+k[v][1],k[v][0]});
	}
	if(!key[u]) {if(son) k[u][0]=k[u][0]*f[u][0],k[u][1]=k[u][1]*f[u][1];}
	else k[u][0]=pii(1,0),k[u][1]=pii(0,1),son=u;
	return son;
}
void solve(int u){
	F[u][0]=fix[u][1]?0:f[u][0], F[u][1]=fix[u][0]?0:f[u][1];
	for(int i=0,lim=G[u].size(),v;i<lim;i++){
		solve(v=G[u][i].v); pii k0=G[u][i].k0,k1=G[u][i].k1;
		F[u][0]=1ll*F[u][0]*((1ll*k0.fi*F[v][0]+1ll*k0.se*F[v][1])%mod)%mod;
		F[u][1]=1ll*F[u][1]*((1ll*k1.fi*F[v][0]+1ll*k1.se*F[v][1])%mod)%mod;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
	dfs(1,0),key[1]=1,build(1);
	for(int s=0;s<1<<cnt;s++){
		for(int i=1;i<=cnt;i++) fix[e[i].x][0]=fix[e[i].x][1]=fix[e[i].y][0]=fix[e[i].y][1]=0;
		for(int i=1;i<=cnt;i++)
			if(s>>i-1&1) fix[e[i].x][1]=fix[e[i].y][0]=1;
			else fix[e[i].x][0]=1;
		solve(1),ans=(1ll*ans+F[1][0]+F[1][1])%mod;
	}
	printf("%d\n",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章