[题解]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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效