路径交集并拓展

路径交集并拓展

HDU 6200

题目要求

给定一个n个点m条边的图 有两种操作
1.加一条u到v的边
2.询问u到v上所有路径的交集

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include<bits/stdc++.h>
#define maxn 100050
#define mm(a,b) memset(a,b,sizeof(a))
using namespace std;
//树剖求lca+dfs序部分
int sz[maxn],dep[maxn],fa[maxn],son[maxn];
int in[maxn],out[maxn],top[maxn];
int t,n,m,q,step;
vector<int>e[maxn];
vector<pair<int,int> >edges;
void init1(){
for(int i=0;i<=n;i++) e[i].clear();
edges.clear();
mm(sz,0),mm(dep,0),mm(fa,0),mm(son,0);
mm(in,0),mm(out,0),mm(top,0);
step=0;
}
void dfs1(int x,int f,int deep){
sz[x]=1,fa[x]=f,son[x]=0,dep[x]=deep;
int len=e[x].size();
for(int i=0;i<len;i++){
int next=e[x][i];
if(next==f) continue;
dfs1(next,x,deep+1);
sz[x]+=sz[next];
if(sz[son[x]]<sz[next]) son[x]=next;
}
}
void dfs2(int x,int tp){
top[x]=tp;
in[x]=++step;
if(son[x]) dfs2(son[x],tp);
int len=e[x].size();
for(int i=0;i<len;i++){
int next=e[x][i];
if(next==fa[x] || next==son[x]) continue;
dfs2(next,next);
}
out[x]=step;
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
return u;
}
//并查集部分
int p[maxn];
void init2(){
for(int i=0;i<=n;i++) p[i]=i;
}
int find(int x){
return p[x]==x?x:p[x]=find(p[x]);
}
void merge(int u,int v){
int x=find(u);
int y=find(v);
if(x!=y) p[x]=y;
}
bool same(int u,int v){
return find(u)==find(v);
}
//树状数组部分:区间更新单点查询
int tree[maxn<<2];
void change(int x,int val){
while(x<=n) {
tree[x]+=val;
x+=x&(-x);
}
}
int sum(int x){
int res=0;
while(x){
res+=tree[x];
x-=x&(-x);
}
return res;
}
//并查集暴力修改
void addedge(int u,int v){
int f=lca(u,v);
while(find(u)!=find(f)){
change(in[find(u)],1);
change(out[find(u)]+1,-1);
merge(find(u),fa[find(u)]);
}
while(find(v)!=find(f)){
change(in[find(v)],1);
change(out[find(v)]+1,-1);
merge(find(v),fa[find(v)]);
}
}
int Case=1;
int main(){
freopen("input.txt","r",stdin);
scanf("%d",&t);
while(t--){
printf("Case #%d:\n",Case++);
scanf("%d%d",&n,&m);
init1(),init2();
mm(tree,0);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
if(same(u,v)) edges.push_back(make_pair(u,v));
else{
e[u].push_back(v),e[v].push_back(u);
merge(u,v);
}
}
init2();
dfs1(1,0,0);
dfs2(1,1);
for(auto p:edges) addedge(p.first,p.second);
scanf("%d",&q);
while(q--){
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(op==1) addedge(x,y);
else{
int f=lca(x,y);
int ans=dep[x]+dep[y]-2*dep[f]-(sum(in[x])+sum(in[y])-2*sum(in[f]));
printf("%d\n",ans);
}
}
}
return 0;
}

思路

在给定的n点m边的图上固定生成一棵树 利用并查集把非树边加到edges中 这些边当成1操作带入到addedge函数中
非树边由于破坏了树的非连通性(树的路径交集就等于长度) 所以必然有某些边构成了环导致并集的减少 所以这些非树边要涂成黑色
所以每当有非树边加进来 等价于把这条非树边涂黑 用树状数组维护路径中到每个点的黑边数目 最后只要查询u到v上的白边数量即可
详细
用树剖求出lca和dfs序 每次涂黑操作需要注意把它并查集根下的所有子树都涂成黑色 带入in的序号是u并查集根的序号
即是in[find(u)]而不是find(u) 这是因为双向边的影响 最终会影响到u的子树和u到u并查集的根这两段 即最终影响到u并查集根下的子树
最后的求ans操作就是u到v上的所有路径减去涂成黑色的路径 就是所求的交集
注意
1.并查集需要初始化2次 在固定生成一棵树后需要初始化
2.加非树边操作同时伴随着merge操作 这意味的同一个并查集下不可能加入2条相同的边 相同的边会在addedge中由于find相等而被略过
保证了算法的正确性

文章目录
  1. 1. 路径交集并拓展
    1. 1.1. HDU 6200
      1. 1.1.1. 题目要求
      2. 1.1.2. 参考AC代码
      3. 1.1.3. 思路
|