NOIP2022T3建造军营题解
NOIP2022T3建造军营题解
[NOIP2022] 建造军营
题目描述
A 国与 B 国正在激烈交战中,A 国打算在自己的国土上建造一些军营。
A 国的国土由 \(n\) 座城市组成,\(m\) 条双向道路连接这些城市,使得任意两座城市均可通过道路直接或间接到达。A 国打算选择一座或多座城市(至少一座),并在这些城市上各建造一座军营。
众所周知,军营之间的联络是十分重要的。然而此时 A 国接到情报,B 国将会于不久后袭击 A 国的一条道路,但具体的袭击目标却无从得知。如果 B 国袭击成功,这条道路将被切断,可能会造成 A 国某两个军营无法互相到达,这是 A 国极力避免的。因此 A 国决定派兵看守若干条道路(可以是一条或多条,也可以一条也不看守),A 国有信心保证被派兵看守的道路能够抵御 B 国的袭击而不被切断。
A 国希望制定一个建造军营和看守道路的方案,使得 B 国袭击的无论是 A 国的哪条道路,都不会造成某两座军营无法互相到达。现在,请你帮 A 国计算一下可能的建造军营和看守道路的方案数共有多少。由于方案数可能会很多,你只需要输出其对 \(1,000,000,007\left(10^{9}+7\right)\) 取模的值即可。两个方案被认为是不同的,当且仅当存在至少一 座城市在一个方案中建造了军营而在另一个方案中没有,或者存在至少一条道路在一个 方案中被派兵看守而在另一个方案中没有。
输入格式
第一行包含两个正整数 \(n,m\),分别表示城市的个数和双向道路的数量。
接下来 \(m\) 行,每行包含两个正整数 \(u_{i},v_{i}\),描述一条连接 \(u_{i}\) 和 \(v_{i}\) 的双向道路。保证没有重边和自环。
输出格式
输出一行包含一个整数,表示建造军营和看守道路的方案数对 \(1,000,000,007\left(10^{9}+ 7\right)\) 取模的结果。
对所有数据,保证 \(1 \leq n \leq 5 \times 10^5\),\(n - 1 \leq m \leq 10^6\)
题解
EX的计数题
显然先缩点,每个边双连通分量内部都是随便选边和选点的。由于这是一张无向连通图,所以缩点后是一棵树,对于计数题,不难想到是树形DP。考虑如何求解:
首先,由于缩点后建立的虚树要考虑每一个点所表示的边双连通分量的边数和点数,很烦,怎么办?也许我们可以采用某些方法使得我们只需要考虑一个?不难发现,我们可以先对总的方案数除以 \(2^m\),这样就消去了边双连通分量内部的边的影响。但这样又会影响其他方案(树边是可以不被保护的)。那么考虑在选择保护一条树边的时候,就可以将答案的贡献除以 \(2\),最后在乘回来的时候就等价于这条边不算(反正有乘法分配律,怎么玩都是对的)。
接着,考虑如何树形DP。最简单直接的套路是设 \(f_i\) 表示以 \(i\) 为根的子树的答案,这时候我们就需要考虑怎么维护了。由于我们要考虑每个点表示的边双连通分量,不妨设 \(size_i\) 表示这个边双连通分量的大小,那么将所有的 \(f_i\) 初始化为 \(2^{size}-1\)。(不能一个都不选。)
然后我们考虑转移,设 \(u\) 节点的儿子节点分别为 \(v_1\sim v_c\),那么由于 \(v\) 的答案已经统计完毕,需要考虑不同的 \(v\) 以及 \(u\) 之间的关系。不难发现,但凡一个 \(v_i\) 要与其他的 \(v\) 或者 \(u\) 组合,那么边$ (u,v_i)$ 就必须得保护。上文已经提到,我们先假设的是每一条边都可选可不选的,最后乘上 \(2^m\) 。所以我们可以先设 \(f'_{v_i}=\frac{f_{v_i}}{2}\),那么 \(f_u\) 就应该是枚举 \(u,v_1\sim v_c\) 的全部组合进行求和,也即 \(2^{c+1}\) 种不同的组合。对于这个转移,我们设 \(f_u^i\) 表示计算完 \(u\) 和前 \(i\) 棵子树组合的答案,则有:
前一节是之前的答案,后面两个式子是算上 \(f_{v'}\) 的。这里有一个细节,我们在只选 \(v_i\) 的时候也应该累计上 \(f'_{v_i}\) 才对,而不应该累计上 \(f_{v_i}\) (因为如果这个子树外有哪个节点被选了,这里都是必须保护的,而没有选的状态已经在 \(f_{v_i}\) 累计过了,而 \(ans\) 只会统计当前树 \(u\) 的次数或者是 \(u\) 的两个及其以上子树(或者是 \(u\) 以及其他子树)进行组合的情况,此时是必然选了子树 \(v\) 中选了的点到 \(u\) 的边的。)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 5000050
#define p 1000000007
int edcc,c[N],dfn[N],low[N],ans,a[N],num,tot=1,cnt,n,m,head[N],nxt[N],ver[N],inv=(p+1)>>1,bridge[N];
int shead[N],snxt[N],sver[N],f[N],stot=1;
void add_s(int u,int v){
snxt[++stot]=shead[u],sver[shead[u]=stot]=v;
}
void add(int u,int v){
nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs(int u,int fa){
ans=(ans+f[u])%p;
for(int i=shead[u];i;i=snxt[i]){
int v=sver[i];
if(v!=fa){
dfs(v,u);
f[v]=(1ll*f[v]*inv)%p;
ans=(1ll*ans+1ll*f[u]*f[v]%p)%p;
f[u]=(1ll*f[u]+1ll*f[v]+1ll*f[u]*f[v]%p)%p;
}
}
}
void tarjan(int u,int in){
// cout<<u<<endl;
dfn[u]=low[u]=++num;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]){
bridge[i]=bridge[i^1]=1;
}
}
else if(i!=(in^1))low[u]=min(low[u],dfn[v]);
}
// cout<<u<<endl;
}
void dfs2(int u,int num){
c[u]=num;
for(int i=head[u];i;i=nxt[i]){
if(bridge[i]||c[ver[i]])continue;
dfs2(ver[i],num);
}
}
void init_1(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
for(int i=1;i<=n;i++)if(!c[i])dfs2(i,++edcc);
}
void init_2(){
for(int i=1;i<=edcc;i++)f[i]=1;
for(int i=1;i<=n;i++)f[c[i]]=1ll*f[c[i]]*2%p;
for(int i=1;i<=edcc;i++)f[i]--;
for(int i=2;i<=tot;i+=2){
int u=ver[i],v=ver[i^1];
if(c[u]==c[v])continue;
add_s(c[u],c[v]);
add_s(c[v],c[u]);
}
// cout<<edcc<<"\n";
dfs(1,0);
for(int i=1;i<=m;i++)ans=1ll*ans*2%p;
cout<<(ans%p+p)%p<<"\n";
}
signed main(){
ios::sync_with_stdio(false);
// freopen("barrack4.in","r",stdin);
init_1();
// cout<<"AS"<<endl;
init_2();
}