[题解]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]\)。
- 如果\(v\)之前都没有建造过军营,那么\(v\)点必须建造军营。
时间复杂度\(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;
}