[题解]P8867 [NOIP2022] 建造军营

P8867 [NOIP2022] 建造军营

只有B国袭破坏的道路是无向图的割边时,这张图才会变得不连通,所以我们进行边双缩点,最终形成一棵树,不妨令根节点为\(1\)

\(E[u]\)为缩点后的\(u\)包含多少条原图上的边,\(V[u]\)\(u\)包含多少个原图上的点,并定义\(s[u]\)表示子树\(u\)中的边数。

那么点\(u\)不建造军营的选择是\(2^{E[u]}\),建造军营的选择是\((2^{V[u]}-1)\times 2^{E[u]}\)

我们设\(f[u][i=0/1]\)表示子树\(u\)选择军营的状态为\(i\)\(i=0\)表示不选,\(i=1\)表示至少选\(1\)个)的答案。规定子树\(u\)外不建军营,也不选边。

我们思考答案如何统计。

  • 对于\(u\ne 1\),有\(ans\leftarrow ans+f[u][1]\times 2^{s[1]-s[u]-1}\)
    \(s[1]-s[u]\)是子树\(u\)之外的边数,这里减去的一个\(1\)\(u\)\(fa[u]\)的边,我们之所以不统计是为了不重复,下面会说到。
  • 对于\(u=1\),有\(ans\leftarrow ans+f[u][1]\)

之所以这样设计状态并统计答案是为了不重不漏。我们发现,如果每个答案都在它所建造的所有军营的LCA处进行统计,就可以做到不重不漏。此处我们在定义处限制“不能有军营建在子树外”,统计处限制“\(u\)\(fa[u]\)的边不选”,其实就是保证了这个条件。


接下来考虑状态转移。

  • 对于\(f[u][0]\),有转移\(f[u][0]=\prod\limits_{\forall v,fa[v]=u}2\times f[v][0]\)
    \(u\)\(v\)的边可选可不选,所以要\(\times 2\)

  • 对于\(f[u][1]\),考虑每新增一个子节点\(v\)对它的贡献。

    • 如果\(v\)之前都没有建造过军营,那么\(v\)点必须建造军营。
      所以有\(f[u][1]\leftarrow f[u][1]+f[u][0]\times f[v][1]\)
      注意此处的\(f[u][0]\)\(v\)之前的答案。
    • 如果\(v\)之前已经建造过军营了,那么\(v\)建不建无所谓。且如果\(v\)不建,那么和\(u\)连不连边都可以。
      所以有\(f[u][1]\leftarrow f[u][1]\times (2f[v][0]+f[v][1])\)

    综上,每遍历到一个子节点\(v\),有\(f[u][1]\leftarrow f[u][1]\times (2f[v][0]+f[v][1])+f[u][0]\times f[v][1]\)

时间复杂度\(O(n\log (n+m))\),其中\(O(\log (n+m))\)是快速幂。


点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 500010
#define mod 1000000007
using namespace std;
int n,m,dfn[N],low[N],tim,st[N],top;
int num[N],V[N],E[N],cnt,s[N],f[N][2],ans;
bitset<N> in;
vector<int> G[N],Ga[N];
void tarjan(int u,int fa){
	dfn[u]=low[u]=++tim,in[st[++top]=u]=1;
	for(int i:G[u]){
		if(i==fa) continue;
		if(!dfn[i]) tarjan(i,u),low[u]=min(low[u],low[i]);
		else if(in[i]) low[u]=min(low[u],dfn[i]);
	}
	if(dfn[u]==low[u]){
		cnt++;
		int x;
		do in[x=st[top--]]=0,num[x]=cnt,V[cnt]++;
		while(x!=u);
	}
}
int qpow(int a,int b){
	int ans=1;
	while(b){
		if(b&1) ans=ans*a%mod;
		a=a*a%mod,b>>=1;
	}
	return ans;
}
void dfs(int u,int fa){
	s[u]=E[u];
	for(int i:Ga[u]) if(i!=fa) dfs(i,u),s[u]+=s[i]+1;
}
void dp(int u,int fa){
	for(int i:Ga[u]){
		if(i==fa) continue;
		dp(i,u);
		f[u][1]=(f[u][1]*(((f[i][0]<<1)+f[i][1])%mod)%mod+f[u][0]*f[i][1]%mod)%mod;
		f[u][0]=f[u][0]*((f[i][0]<<1)%mod)%mod;
	}
	if(u==1) ans=(ans+f[u][1])%mod;
	else ans=(ans+f[u][1]*qpow(2,s[1]-s[u]-1))%mod;
}
signed main(){
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		G[u].emplace_back(v);
		G[v].emplace_back(u);
	}
	tarjan(1,0);
	for(int i=1;i<=n;i++){
		for(int j:G[i]){
			if(num[i]!=num[j]) Ga[num[i]].emplace_back(num[j]);
			else E[num[i]]++;
		}
	}
	for(int i=1;i<=cnt;i++){
		E[i]>>=1;
		f[i][0]=qpow(2,E[i]);
		f[i][1]=qpow(2,V[i]+E[i])-f[i][0];
	}
	dfs(1,0),dp(1,0);
	cout<<ans<<"\n";
	return 0;
}
posted @ 2024-11-27 15:36  Sinktank  阅读(8)  评论(0编辑  收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.