[NOIP2022] 建造军营 题解
[NOIP2022] 建造军营 题解
知识点
边双连通分量,树形 DP。
题意简述
一张 \(n\) 个点,\(m\) 条边的无向连通图,现在选出任意个特殊点,要求再选出一种选边方案使得:断掉任意一条不被选的边,所有特殊点仍连通。
现在问所有选特殊点和对应选边方案的总数。
分析
首先可以看出我们需要边双连通分量缩点,然后就可以把割边独立出来,形成一棵树。
然后我们在选边时只用考虑割边,剩下的可选可不选,不会影响连通性。
然后这就变成了一个显然的树形 DP:
状态设计
设 \(f_u\) 表示第 \(u\) 个边双连通分量中有特殊点时,以 \(u\) 为根的整棵子树有多少中方案。
转移
设 \(Road_u\) 代表 \(u\) 中有几条两端点都属于 \(u\) 的边,\(road_u\) 表示以 \(u\) 为根的子树中有几条两端点都在子树内的边,\(city_u\) 表示 \(u\) 中有几个点。
那么我们就可以得到转移:假设现在转移到 \(u\).
-
从子结点转移:
\[f' = \prod_{v \in Son_u} (2^{road_v+1}+f_v) \\ \]我们这里得到的是以 \(u\) 为根的子树中除去 \(u\) 中选取情况的方案数 \(f'\)。
-
\(2^{road_v+1}\) 这一项代表的是:如果子树内没有特殊点,那么边有 \(2^{road_v}\) 种选择方案,而这一条割边有 \(2\) 种选择方案。
-
\(f_v\) 这一项则代表割边必须选择,只有 \(1\) 种方案。
-
-
处理自身:
\[\begin{aligned} f_u & = f'2^{city_u+Road_u}-2^{road_u}\\ \end{aligned} \]-
前一项 \(f'2^{city_u+Road_u}\) 是将所有子孙结点方案数乘上自己这个连通分量内随意选择的方案数。
-
后一项 \(-2^{road_u}\) 是为了把整棵子树一个节点不选的方案去除。
-
统计答案
我们最后不能直接输出 \(f_1\),因为按照定义,\(f_u\) 是必须选择连通分量 \(u\) 中的某个点作为特殊点时的情况。
然后我们发现由于 \(f_u\) 向父结点转移的时候,割边是必须选的,而我们所差的就是割边不选的方案。
割边不选又导致子树外的点都是不能作为特殊点的,所以最终方案就变成了 \(f_u 2^{m-1-road_u}\)。当然,子树根节点要特判。
答案为:
代码
时空复杂度:\(O(n+m)\)。
#define Plus_Cat "barrack"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i((g).h[x]),y((g)[i].v);~i;y=(g)[i=(g)[i].nxt].v)
using namespace std;
namespace IOstream {
#define getc() getchar()
#define putc(c) putchar(c)
#define isdigit(c) ('0'<=(c)&&(c)<='9')
template<class T>void rd(T &x) {
static char ch(0);
for(x=0,ch=getc(); !isdigit(ch); ch=getc());
for(; isdigit(ch); x=(x<<1)+(x<<3)+(ch^48),ch=getc());
}
template<class T>void wr(T x,const char End='\n') {
static int top(0);
static int st[50];
do st[++top]=x%10,x/=10;
while(x);
while(top)putc(st[top]^48),--top;
putc(End);
}
} using namespace IOstream;
constexpr int N(5e5+10),M(1e6+10);
namespace Modular {
#define Mod 1000000007
int pw[M];
template<class T1,class T2>constexpr auto add(const T1 a,const T2 b) {
return a+b>=Mod?a+b-Mod:a+b;
}
template<class T1,class T2>T1 &toadd(T1 &a,const T2 b) {
return a=add(a,b);
}
template<class T1,class T2>constexpr auto mul(const T1 a,const T2 b) {
return (ll)a*b%Mod;
}
template<class T1,class T2>T1 &tomul(T1 &a,const T2 b) {
return a=mul(a,b);
}
void Init(int n=M-5) {
FOR(i,pw[0]=1,n)pw[i]=mul(pw[i-1],2);
}
} using namespace Modular;
int ans;
namespace eDCC {
int n,m,dc,idx,top;
int f[N],u[M],v[M],st[N],dcc[N],dfn[N],low[N],city[N],road[N];
template<const int N,const int M>struct CFS {
int tot,h[N];
struct edge {
int v,nxt;
edge(int v=0,int nxt=-1):v(v),nxt(nxt) {}
} e[M];
edge &operator[](int i) {
return e[i];
}
void Init(int n) {
tot=-1,RCL(h+1,-1,int,n);
}
void att(int u,int v) {
e[++tot]=edge(v,h[u]),h[u]=tot;
}
void con(int u,int v) {
att(u,v),att(v,u);
}
};
CFS<N,M<<1> g;
CFS<N,N<<1> G;
void tarjan(int u,int pa) {
dfn[u]=low[u]=++idx,st[++top]=u;
EDGE(g,i,u,v)if((i^1)!=pa) {
if(!dfn[v])tarjan(v,i),tomin(low[u],low[v]);
else tomin(low[u],dfn[v]);
}
if(low[u]==dfn[u]) {
++dc;
do ++city[dcc[st[top]]=dc];
while(st[top--]^u);
}
}
void Build() {
rd(n),rd(m),g.Init(n);
FOR(i,1,m)rd(u[i]),rd(v[i]),g.con(u[i],v[i]);
}
void Rebuild() {
G.Init(dc);
FOR(u,1,n)EDGE(g,i,u,v) {
if(dcc[u]^dcc[v])G.att(dcc[u],dcc[v]);
else ++road[dcc[u]];
}
FOR(i,1,dc)road[i]>>=1;
}
void dfs(int u,int fa) {
f[u]=1;
int Road(road[u]);
EDGE(G,i,u,v)if(v^fa)dfs(v,u),tomul(f[u],add(pw[road[v]+1],f[v])),road[u]+=road[v]+1;
f[u]=add(mul(f[u],mul(pw[city[u]],pw[Road])),Mod-pw[road[u]]);
if(!fa)toadd(ans,f[u]);
else toadd(ans,mul(f[u],pw[m-road[u]-1]));
}
} using namespace eDCC;
int main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
Build(),Init(),tarjan(1,-1),Rebuild(),dfs(1,0),wr(ans);
return 0;
}