P8867 [NOIP2022] 建造军营 题解

边双缩点,记 \(E_i\) 表示 \(i\) 号边双内部的边数,\(V_i\) 为点数。

\(f_{u,0/1}\) 表示 \(u\) 子树内建/不建军营,且 \(u\) 子树外点强制不选,\(u\to fa\) 这条边强制不选,子树内建造的军营(如果有)通过被保护的边一定与 \(u\) 相连的方案数。

初始有 \(f_{u,0}=2^{E_u},f_{u,1}=2^{V_u+E_u}-2^{E_u}\)

考虑答案的统计:记 \(s_u\) 表示 \(u\) 子树内的 \(E\) 和,那么 \(u\) 对答案的贡献就是 \(f_{u,1}\times 2^{s_1-s_u-1}\)(不能不建军营,且 \(u\to fa\) 强制不选)。注意特判 \(u\) 为根节点的情况,此时贡献为 \(f_{u,1}\)

显然 \(s_u=E_u+\sum_{v\in son}(s_v+1)\)

接下来考虑转移,设当前儿子为 \(v\)

对于 \(f_{u,0}\) 来说,到 \(v\) 的边选不选都行,所以贡献为 \(2f_{v,0}\)。最终把所有儿子的贡献和自己的初始值乘起来就是答案。

对于 \(f_{u,1}\) 来说,采用树上背包的方式转移。有两种情况:

  • 之前还没有选点

那么 \(v\) 就必须选了,加上 \(f_{u,0}\times f_{v,1}\)

  • 之前已经选了

那么 \(v\) 可选可不选,注意如果 \(v\) 不选那么 \(u\to v\) 也可以不选。加上 \(f_{u,1}\times(2f_{v,0}+f_{v,1})\)

注意 \(f_{u,1}\) 的转移需要用到 \(f_{u,0}\),所以要先转移 \(f_{u,1}\)

以上操作均可在 \(O(n)\) 的时间复杂度内完成。

代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
#define fst first
#define scd second
typedef long long ll;
const int N=5e5+5;
const int M=1e6+5;
const int mod=1e9+7;
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> pii;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
	x=0;bool sgn=0;char ch=gt();
	while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
	while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
	if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x,T1 &...x1){
	read(x);
	read(x1...);
}
template<class T> inline void print(T x){
	static char st[70];short top=0;
	if(x<0) pt('-');
 	do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
    while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
	print(x);
	putchar(' ');
}
template<class T> inline void println(T x){
	print(x);
	putchar('\n');
}
int n,m,pw[N+M];
ll ans,f[N][2];
vector<int>g[N];
struct edge{
	int to,nxt;
}e[M<<1];
int head[N],cnt=1;
inline void add_edge(int f,int t){
	e[++cnt].to=t;
	e[cnt].nxt=head[f];
	head[f]=cnt;
}
inline void add_double(int f,int t){
	add_edge(f,t);
	add_edge(t,f);
}
int id[N],dfn[N],low[N],ti,cnt_dcc,E[N],V[N],s[N];
bool cut[M<<1];
void tarjan(int u,int from){
	dfn[u]=low[u]=++ti;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(i==(from^1)) continue;
		if(!dfn[v]){
			tarjan(v,i);
			if(dfn[u]<low[v]) cut[i]=cut[i^1]=1;
			low[u]=min(low[u],low[v]);
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
void dfs(int u,int dcc_id){
	id[u]=dcc_id;
	V[dcc_id]++;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(id[v]||cut[i]) continue;
		dfs(v,dcc_id);
	}
}
void dfs_init(int u,int fa){
	s[u]=E[u];
	for(int v:g[u]){
		if(v==fa) continue;
		dfs_init(v,u);
		s[u]+=(s[v]+1);
	}
}
void dfs_dp(int u,int fa){
	for(int v:g[u]){
		if(v==fa) continue;
		dfs_dp(v,u);
		f[u][1]=(f[u][1]*(2*f[v][0]+f[v][1])%mod+f[u][0]*f[v][1]%mod)%mod;
		f[u][0]=(f[u][0]*2*f[v][0])%mod;
	}
	if(u!=1) ans=(ans+f[u][1]*pw[s[1]-s[u]-1])%mod;
	else ans=(ans+f[u][1])%mod;
}
signed main(){
	read(n,m);
	pw[0]=1;
	for(int i=1;i<=n+m;++i) pw[i]=pw[i-1]*2%mod;
	for(int u,v,i=1;i<=m;++i){
		read(u,v);
		add_double(u,v);
	}
	tarjan(1,0);
	for(int i=1;i<=n;++i) if(!id[i]) dfs(i,++cnt_dcc);
	for(int u=1;u<=n;++u){
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(id[u]==id[v]) E[id[u]]++;
			else g[id[u]].emplace_back(id[v]);
		}
	}
	for(int i=1;i<=cnt_dcc;++i){
		E[i]>>=1;
		f[i][0]=pw[E[i]];
		f[i][1]=(pw[V[i]+E[i]]-pw[E[i]]+mod)%mod;
	}
	dfs_init(1,0);
	dfs_dp(1,0);
	println(ans);
	return 0;
}
posted @ 2024-02-28 15:08  Southern_Dynasty  阅读(72)  评论(0编辑  收藏  举报