[题解][省选联考 2021 A 卷]支配
壹、题目描述 ¶
贰、题解 ¶
可以先得到原图的支配树,使用 \(\mathcal O(n^2)\) 或者 \(\mathcal O(n\log n)\) 均可。
考虑加入一条边之后,受支配集发生变化的点,反映到支配树上即,某个点 \(u\) 的某个祖先 \(anc\) 在新的支配树上不再是他的祖先了。
如果将所有这样的点全部找出来,显然不可取。
感谢 lindongli2004 的思路 给我灵感,考虑当一个点的受支配集发生变化,显然,他在原支配树上的所有后代的受支配集同样发生了变化,所以,我们只需要找在支配树上深度最浅的点,其某个祖先在新支配树上不再是其祖先,而这个深度最浅的点,发生变化的就不止是他的祖先,这个祖先更是他的父亲,不然就还有深度比他更浅的点。
也就是说,我们要找到在原支配树上,在加入 \(\lang x, y\rang\) 之后,某个点的父亲不再支配他的点,找到这样的点之后,他和他的子树都被算入答案贡献中,然后就不用再询问他的子树了。
显然,当一个点 \(u\) 的父亲 \(t\) 在加入 \(\lang x,y\rang\) 之后不再支配他,当且仅当存在一条 \(1\rightarrow x\rightarrow y\rightarrow u\) 不经过他的父亲,将这个路径拆成三个部分 这不是显然的吗,即 \(1\rightarrow x,x\rightarrow y,y\rightarrow u\),也就是要满足这样几个条件:
- 存在一条 \(1\rightarrow x\) 的路径不经过 \(t\);
- \(x\rightarrow y\) 不经过 \(t\);
- 存在 \(y\rightarrow u\) 的路径不经过 \(t\);
对于第一个条件,转化一下,即 \(x\) 在原图上不受 \(t\) 的支配,我们只需要将所有 \(x\) 的祖先暴力打上标记就可以判断了,时间复杂度 \(\mathcal O(n)\).
对于第二个条件,判一判就好。
对于第三个条件,考虑预处理一个 \(f_{i,j}\) 表示 \(j\) 能否在不经过 \(i\) 的支配树上的父亲到达 \(i\),具体预处理方式就是枚举 \(i\),然后在原图的反图中 \(\tt BFS\) 一遍,途中不能扩展 \(i\) 的支配树父亲,复杂度 \(\mathcal O(n(n+m))\le \mathcal O(3n^2)\).
对于每个询问,枚举一个 \(u\),判断 \(f_{u,y}\) 并且看 \(father_u\) 是否支配 \(x\) 即可。
总复杂度 \(\mathcal O(n\log n+3n^2+nq)\).
叁、参考代码 ¶
用的 \(\mathcal O(n\log n)\) 构建支配树的方法。
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
const int maxn=3e3;
const int maxm=maxn*2;
const int logn=12;
struct graph{
struct edge{
int to, nxt;
edge(){}
edge(int T, int N): to(T), nxt(N){}
}e[maxm*2+5];
int tail[maxn+5], ecnt;
inline void add_edge(int u, int v){
e[ecnt]=edge(v, tail[u]); tail[u]=ecnt++;
}
graph(){
ecnt=0;
memset(tail, -1, sizeof tail);
}
}G, rG, dag, rdag;
#define foredge(i, u, G) for(int i=G.tail[u], v; ~i? (v=G.e[i].to, 1): 0; i=G.e[i].nxt)
int n, m, q;
inline void input(){
n=readin(1), m=readin(1), q=readin(1);
int u, v;
for(int i=1; i<=m; ++i){
u=readin(1), v=readin(1);
G.add_edge(u, v);
rG.add_edge(v, u);
}
}
int dfn[maxn+5], refl[maxn+5], fa[maxn+5], timer;
void dfs(int u){
refl[dfn[u]=++timer]=u;
foredge(i, u, G) if(!dfn[v]){
fa[v]=u, dag.add_edge(u, v);
dfs(v);
}
}
int pre[maxn+5], minn[maxn+5], sdom[maxn+5];
inline void initial(){
for(int i=1; i<=n; ++i)
pre[i]=minn[i]=sdom[i]=i;
}
int findrt(int u){
if(u==pre[u]) return u;
int ret=findrt(pre[u]);
if(dfn[sdom[minn[u]]]>dfn[sdom[minn[pre[u]]]])
minn[u]=minn[pre[u]];
return pre[u]=ret;
}
inline void tarjan(){
initial();
for(int j=n; j>1; --j){
int u=refl[j], res=j;
if(!u) continue;
foredge(i, u, rG){
if(!dfn[v]) continue;
if(dfn[v]<dfn[u]) res=min(res, dfn[v]);
else findrt(v), res=min(res, dfn[sdom[minn[v]]]);
}
sdom[u]=refl[res];
pre[u]=fa[u];
dag.add_edge(sdom[u], u);
}
}
int in[maxn+5], Q[maxn+5], op, ed;
vector<int>tre[maxn+5];
int tp[maxn+5][logn+5], dep[maxn+5];
inline int getlca(int u, int v){
if(dep[u]<dep[v]) swap(u, v);
for(int j=logn; j>=0; --j) if(dep[tp[u][j]]>=dep[v])
u=tp[u][j];
if(u==v) return u;
for(int j=logn; j>=0; --j) if(tp[u][j]!=tp[v][j])
u=tp[u][j], v=tp[v][j];
return tp[u][0];
}
inline void insert_tre(int u){
int par=rdag.e[rdag.tail[u]].to;
foredge(i, u, rdag) par=getlca(par, v);
dep[u]=dep[par]+1, tp[u][0]=par;
tre[par].push_back(u);
for(int j=1; j<=logn; ++j)
tp[u][j]=tp[tp[u][j-1]][j-1];
}
inline void topu(){
for(int u=1; u<=n; ++u){
foredge(i, u, dag){
++in[v];
rdag.add_edge(v, u);
}
}
for(int i=1; i<=n; ++i) if(!in[i]){
dag.add_edge(0, i);
rdag.add_edge(i, 0);
++in[i];
}
Q[op=ed=1]=0;
while(op<=ed){
int u=Q[op++];
foredge(i, u, dag) if(!--in[v]){
Q[++ed]=v;
insert_tre(v);
}
}
}
int siz[maxn+5];
void dfstre(int u){
siz[u]=1, refl[dfn[u]=++timer]=u;
for(int i=0, up=tre[u].size(); i<up; ++i)
dfstre(tre[u][i]), siz[u]+=siz[tre[u][i]];
}
int f[maxn+5][maxn+5];
// whether i can reach j without going by i's father in the dominant_tre
inline void getf(){
// besides 1(root)
for(int i=2; i<=n; ++i){
Q[op=ed=1]=i, f[i][i]=1;
while(op<=ed){
int u=Q[op++];
foredge(j, u, rG) if(!f[i][v] && v!=tp[i][0]){
f[i][v]=1, Q[++ed]=v;
}
}
}
}
int vis[maxn+5];
inline void climb(int u){
while(u) vis[u]=1, u=tp[u][0];
}
inline void getquery(){
int x, y, ans;
while(q--){
x=readin(1), y=readin(1), ans=0;
memset(vis, 0, sizeof vis); climb(x);
// pay attention, dfn[0]=1, refl[1]=0, so the real dfn is [2, n+1]
for(int i=2; i<=n+1; ++i){
int u=refl[i];
if(f[u][y] && !vis[tp[u][0]])
ans+=siz[u], i+=siz[u]-1;
}
printf("%d\n", ans);
}
}
signed main(){
input();
dfs(1);
tarjan();
topu();
// rebuild dfn[], get siz[]
timer=0; dfstre(0);
getf();
getquery();
return 0;
}
肆、用到 の \(\tt trick\) ¶
这一段很妙啊:
考虑当一个点的受支配集发生变化,显然,他在原支配树上的所有后代的受支配集同样发生了变化,所以,我们只需要找在支配树上深度最浅的点,其某个祖先在新支配树上不再是其祖先,而这个深度最浅的点,发生变化的就不止是他的祖先,这个祖先更是他的父亲,不然就还有深度比他更浅的点。
当要统计哪些点的祖先发生变化的时候,实际上找最上面的点就行了,而这样找能将 “祖先” 转变成 “父亲”,这就好处理得多。