圆方树学习笔记

First Post:

Last Update:

Word Count:
5.8k

Read Time:
32 min

经常见到圆方树,但一直不会,正好在复习连通性,顺便学一下。

但我学的是广义圆方树,所以下文都是有关广义圆方树的介绍。

定义及构建

在圆方树中,原图的每个点对应一个圆点,每个点双对应一个方点。

在一个联通分量内,对于每个点双,我们把它对应的方点和在其内的所有点对应的圆点连边,会得到一棵树。

这棵树就叫圆方树。

如果图由若干个联通分量构成,则会形成圆方树森林。

特别的,对于孤点应该特殊处理(然而我的处理就是不管它但一直没错过)。

以下是对无向图建圆方树的一个图示:

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
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=2e5+10,maxm=5e5+10;
int n,m,cnt,tim,top,tot;
int head[maxn];
int dfn[maxn],low[maxn];
int stk[maxn];
struct edge{
int to,nxt;
}e[maxm<<1];
vector<int> g[maxn<<1];
inline void add(int u,int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
inline void tarjan(int u,int fa){
dfn[u]=low[u]=++tim;
stk[++top]=u;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
++tot;int x;
g[tot].push_back(u),g[u].push_back(tot);
do{
x=stk[top--];
g[tot].push_back(x),g[x].push_back(tot);
}while(x!=v);
}
}
else if(v!=fa) low[u]=min(low[u],dfn[v]);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;tot=n;
for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u);
for(re int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,i);
return 0;
}

性质

  1. 圆方树的点数会达到 级别,所以数组别开小。
  2. 树上圆点仅会和方点相连,方点仅会和圆点相连。

  3. 度数大于 1 的圆点在原图中一定是割点。

例题

道路相遇

双倍经验:Traffic Real Time Query System

结论:两个点之间必经点个数=圆方树上对应圆点路径上圆点数。

这里的必经点包含这两个点。

所以建出圆方树后树剖维护即可。

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
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=5e5+10,maxm=1e6+10;
int n,m,Q,cnt,tim,top,tot,id,rt,segcnt;
int head[maxn];
int dfn[maxn],low[maxn];
int stk[maxn];
struct edge{
int to,nxt;
}e[maxm<<1];
struct node{
int fa,siz,top,hson,dep,dfn,seq;
}t[maxn<<1];
struct tree{
int ls,rs,sum;
}tr[maxn<<2];
vector<int> g[maxn<<1];
inline void add(int u,int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
inline void tarjan(int u,int fa){
dfn[u]=low[u]=++tim;
stk[++top]=u;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
++tot;
int x;
do{
x=stk[top--];
g[tot].push_back(x),g[x].push_back(tot);
}while(x!=v);
g[tot].push_back(u),g[u].push_back(tot);
}
}
else if(v!=fa) low[u]=min(low[u],dfn[v]);
}
}
inline void dfs1(int u,int fa){
t[u].fa=fa;
t[u].siz=1;
t[u].dep=t[fa].dep+1;
for(auto v:g[u]){
if(v==fa) continue;
dfs1(v,u);
t[u].siz+=t[v].siz;
if(!t[u].hson||t[v].siz>t[t[u].hson].siz) t[u].hson=v;
}
}
inline void dfs2(int u,int top){
t[u].dfn=++id;
t[id].seq=u;
t[u].top=top;
if(!t[u].hson) return;
dfs2(t[u].hson,top);
for(auto v:g[u]){
if(v==t[u].hson||v==t[u].fa) continue;
dfs2(v,v);
}
}
inline void up(int p){tr[p].sum=tr[tr[p].ls].sum+tr[tr[p].rs].sum;}
inline void build(int l,int r,int &p){
if(!p) p=++segcnt;
if(l==r){tr[p].sum=(t[l].seq<=n);return;}
int mid=(l+r)>>1;
build(l,mid,tr[p].ls),build(mid+1,r,tr[p].rs);
up(p);
}
inline int query(int l,int r,int L,int R,int p){
if(l>=L&&r<=R) return tr[p].sum;
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(l,mid,L,R,tr[p].ls);
if(R>mid) res+=query(mid+1,r,L,R,tr[p].rs);
return res;
}
inline int query(int u,int v){
int res=0;
while(t[u].top!=t[v].top){
if(t[t[u].top].dep<t[t[v].top].dep) swap(u,v);
res+=query(1,tot,t[t[u].top].dfn,t[u].dfn,1);
u=t[t[u].top].fa;
}
if(t[u].dep>t[v].dep) swap(u,v);
res+=query(1,tot,t[u].dfn,t[v].dfn,1);
return res;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;tot=n;
for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u);
tarjan(1,0),dfs1(1,0),dfs2(1,1),build(1,tot,rt);
cin>>Q;
while(Q--){
int u,v;
cin>>u>>v;
cout<<query(u,v)<<'\n';
}
return 0;
}

[SDOI2018] 战略游戏

双倍经验:国土规划

上一题的加强版。

这次从两个点变成若干点了,但保证了总和,所以想到虚树。

将每次询问点拎出来建虚树,建完后可以发现:虚树上所有是非询问点的圆点(包括建虚树需要用到的关键点和虚树的边上经过的那些非关键点)都合法。

为了方便,我把 1 也强制加到虚树了,但 1 不一定能产生贡献,所以必须特判。

注意:树剖的重儿子数组要清空!

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
131
132
133
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=2e5+10;
int n,m,Q,cnt;
int top,tim,tot;
int id;
int ans,num,len;
int head[maxn];
int dfn[maxn],low[maxn],stk[maxn];
int a[maxn],b[maxn<<1];
int sum[maxn<<1];
bitset<maxn> vis;
vector<int> g[maxn<<1];
struct node{
int fa,dfn,top,hson,siz,dep;
}t[maxn<<1];
struct edge{
int to,nxt;
}e[maxn<<1];
inline void add(int u,int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
inline void tarjan(int u,int fa){
dfn[u]=low[u]=++tim;
stk[++top]=u;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
++tot;
g[tot].push_back(u),g[u].push_back(tot);
int x;
do{
x=stk[top--];
g[tot].push_back(x),g[x].push_back(tot);
}while(x!=v);
}
} else if(v!=fa) low[u]=min(low[u],dfn[v]);
}
}
inline void dfs1(int u,int fa){
t[u].fa=fa;
t[u].siz=1;
t[u].dep=t[fa].dep+1;
sum[u]=sum[fa]+(u<=n);
for(auto v:g[u]){
if(v==fa) continue;
dfs1(v,u);
t[u].siz+=t[v].siz;
if(!t[u].hson||t[v].siz>t[t[u].hson].siz) t[u].hson=v;
}
}
inline void dfs2(int u,int top){
t[u].top=top;
t[u].dfn=++id;
if(!t[u].hson) return;
dfs2(t[u].hson,top);
for(auto v:g[u]){
if(v==t[u].hson||v==t[u].fa) continue;
dfs2(v,v);
}
}
inline int lca(int u,int v){
while(t[u].top!=t[v].top){
if(t[t[u].top].dep<t[t[v].top].dep) swap(u,v);
u=t[t[u].top].fa;
}
return t[u].dep<t[v].dep?u:v;
}
inline bool cmp(int a,int b){return t[a].dfn<t[b].dfn;}
inline void build(){
len=0;
sort(a+1,a+num+1,cmp);
for(re int i=1;i<num;++i){
b[++len]=a[i];
b[++len]=lca(a[i],a[i+1]);
}
b[++len]=a[num];
b[++len]=1;
sort(b+1,b+len+1,cmp);
len=unique(b+1,b+len+1)-(b+1);
for(re int i=1,lc;i<len;++i){
lc=lca(b[i],b[i+1]);
g[lc].push_back(b[i+1]),g[b[i+1]].push_back(lc);
}
}
inline void dfs3(int u,int fa){
for(auto v:g[u]){
if(v==fa) continue;
ans+=(sum[v]-sum[u]-(v<=n));
dfs3(v,u);
}
if(!vis[u]&&u<=n&&(u!=1||g[u].size()!=1)) ++ans;
if(!vis[u]&&u==1&&g[u].size()==1) ans-=(sum[g[u][0]]-sum[u]-(g[u][0]<=n));
g[u].clear();
}
inline void clear(){
tot=n;
cnt=tim=top=id=0;
for(re int i=1;i<=n;++i) head[i]=0,dfn[i]=0,low[i]=0;
}
inline void solve(){
cin>>n>>m;clear();
for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u);
tarjan(1,0),dfs1(1,0),dfs2(1,1);
for(re int i=1;i<=tot;++i) g[i].clear();
cin>>Q;
while(Q--){
cin>>num;ans=0;
for(re int i=1;i<=num;++i) cin>>a[i],vis[a[i]]=1;
build(),dfs3(1,0);
cout<<ans<<'\n';
for(re int i=1;i<=num;++i) vis[a[i]]=0;
}
for(re int i=1;i<=tot;++i){
sum[i]=0;
t[i].hson=0;
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--) solve();
return 0;
}

[DBOI2019] 巫女的职责

综合版:EntropyIncreaser 与 动态图,动态维护割边割点。

动态维护割点,考虑 LCT 维护圆方树。

如果不考虑加边,那么单点修改和链查询链推平都是 LCT 基础操作。

然后考虑加边。

事实上,只需要在连边前,判断这两个点是否连通,如果已经连通了,那连上这条边就会产生一个环,也就是点双,给路径上的边全部断开然后暴力重构圆方树即可。

复杂度是对的,因为每个点最多只会被合并一次,而每次复杂度都是 的。

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
#include"bits/stdc++.h"
#define re register
#define int long long
#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
#define fa(x) tr[x].fa
#define get(x) (rs(fa(x))==x)
#define isroot(x) (ls(fa(x))!=x&&rs(fa(x))!=x)
using namespace std;
const int maxn=1e6+10;
int n,m,tot,lastans,top;
int stk[maxn];
struct node{
int w,fa,ch[2],tag,siz,add,sum;
}tr[maxn];
inline void up(int x){
tr[x].sum=tr[ls(x)].sum+tr[rs(x)].sum+tr[x].w;
tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz+(x<=n);
}
inline void upadd(int x){
tr[x].sum=tr[x].w=0;
tr[x].add=1;
}
inline void rev(int x){swap(ls(x),rs(x));tr[x].tag^=1;}
inline void down(int x){
if(tr[x].add){
upadd(ls(x));
upadd(rs(x));
tr[x].add=0;
}
if(tr[x].tag){
rev(ls(x)),rev(rs(x));
tr[x].tag=0;
}
}
inline void update(int x){if(!isroot(x)){update(fa(x));}down(x);}
inline void rotate(int x){
int y=fa(x),z=fa(y),t=get(x);
if(!isroot(y)) tr[z].ch[get(y)]=x;
fa(tr[x].ch[t^1])=y,tr[y].ch[t]=tr[x].ch[t^1];
tr[x].ch[t^1]=y;
fa(y)=x,fa(x)=z;
up(y),up(x);
}
inline void splay(int x){
update(x);
for(re int f=fa(x);f=fa(x),!isroot(x);rotate(x)){
if(!isroot(f)) rotate(get(f)==get(x)?f:x);
}
}
inline void access(int x){for(re int p=0;x;p=x,x=fa(x)){splay(x),rs(x)=p,up(x);}}
inline void makeroot(int x){access(x),splay(x),rev(x);}
inline int find(int x){
access(x),splay(x);
while(ls(x)) x=ls(x),down(x);
splay(x);
return x;
}
inline void split(int x,int y){makeroot(x),access(y),splay(y);}
inline void link(int x,int y){makeroot(x);if(find(y)!=x) fa(x)=y;}
inline void cut(int x,int y){makeroot(x);if(find(y)==x&&fa(y)==x&&!ls(y)) fa(y)=rs(x)=0;}
inline const void decode(int &x){x^=lastans%n;if(x>n)x%=n;if(!x)x=1;}
inline int query(int x,int y){
if(find(x)!=find(y)) return 0;
split(x,y);
int res=tr[y].sum;
upadd(y);
return res;
}
inline void dfs(int u){
if(!u) return;down(u);
dfs(ls(u));
stk[++top]=u;
dfs(rs(u));
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;tot=n;
int op,x,y;
while(m--){
cin>>op>>x>>y;
decode(x),decode(y);
if(op==1){
if(find(x)==find(y)){
split(x,y);
if(tr[y].siz<=2) continue;
top=0;dfs(y);++tot;
for(re int i=1;i<top;++i) cut(stk[i],stk[i+1]);
for(re int i=1;i<=top;++i) link(stk[i],tot);
}
else link(x,y);
}
if(op==2) splay(x),tr[x].w+=y;
if(op==3){
lastans=query(x,y);
cout<<lastans<<'\n';
}
}
return 0;
}

综合版代码

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
131
132
133
134
135
136
137
138
139
140
141
#include"bits/stdc++.h"
#define re register
#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
#define fa(x) tr[x].fa
#define get(x) (rs(fa(x))==x)
#define isroot(x) (ls(fa(x))!=x&&rs(fa(x))!=x)
using namespace std;
const int maxn=2e5+10;
int n,m,lastans,tot;
namespace LCT1{
int tot;
struct node{
int w,fa,ch[2],tag,add,sum;
}tr[maxn];
inline void up(int x){tr[x].sum=tr[ls(x)].sum+tr[rs(x)].sum+tr[x].w;}
inline void upadd(int x){
tr[x].sum=tr[x].w=0;
tr[x].add=1;
}
inline void rev(int x){swap(ls(x),rs(x));tr[x].tag^=1;}
inline void down(int x){
if(tr[x].add){
upadd(ls(x)),upadd(rs(x));
tr[x].add=0;
}
if(tr[x].tag){
rev(ls(x)),rev(rs(x));
tr[x].tag=0;
}
}
inline void update(int x){if(!isroot(x)){update(fa(x));}down(x);}
inline void rotate(int x){
int y=fa(x),z=fa(y),t=get(x);
if(!isroot(y)) tr[z].ch[get(y)]=x;
fa(tr[x].ch[t^1])=y,tr[y].ch[t]=tr[x].ch[t^1];
tr[x].ch[t^1]=y;
fa(y)=x,fa(x)=z;
up(y),up(x);
}
inline void splay(int x){
update(x);
for(re int f=fa(x);f=fa(x),!isroot(x);rotate(x)){
if(!isroot(f)) rotate(get(f)==get(x)?f:x);
}
}
inline void access(int x){for(re int p=0;x;p=x,x=fa(x)){splay(x),rs(x)=p,up(x);}}
inline void makeroot(int x){access(x),splay(x),rev(x);}
inline int find(int x){
access(x),splay(x);
while(ls(x)) x=ls(x),down(x);
splay(x);
return x;
}
inline void split(int x,int y){makeroot(x),access(y),splay(y);}
inline void link(int x,int y){makeroot(x);if(find(y)!=x) fa(x)=y;}
inline void cut(int x,int y){makeroot(x);if(find(y)==x&&fa(y)==x&&!ls(y)) fa(y)=rs(x)=0;}
inline int query(int x,int y){
if(find(x)!=find(y)) return -1;
split(x,y);
return tr[y].sum;
}
inline void link1(int x,int y){
int u=find(x),v=find(y);
if(u==v) split(x,y),upadd(y);
else ++tot,tr[tot].w=1,link(x,tot),link(tot,y);
}
}
namespace LCT2{
int tot,top;
int stk[maxn];
struct node{
int fa,ch[2],tag,siz;
}tr[maxn];
inline void up(int x){tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz+(x<=n);}
inline void rev(int x){swap(ls(x),rs(x));tr[x].tag^=1;}
inline void down(int x){if(tr[x].tag) rev(ls(x)),rev(rs(x));tr[x].tag=0;}
inline void update(int x){if(!isroot(x)){update(fa(x));}down(x);}
inline void rotate(int x){
int y=fa(x),z=fa(y),t=get(x);
if(!isroot(y)) tr[z].ch[get(y)]=x;
fa(tr[x].ch[t^1])=y,tr[y].ch[t]=tr[x].ch[t^1];
tr[x].ch[t^1]=y;
fa(y)=x,fa(x)=z;
up(y),up(x);
}
inline void splay(int x){
update(x);
for(re int f=fa(x);f=fa(x),!isroot(x);rotate(x)){
if(!isroot(f)) rotate(get(f)==get(x)?f:x);
}
}
inline void access(int x){for(re int p=0;x;p=x,x=fa(x)){splay(x),rs(x)=p,up(x);}}
inline void makeroot(int x){access(x),splay(x),rev(x);}
inline int find(int x){
access(x),splay(x);
while(ls(x)) x=ls(x),down(x);
splay(x);
return x;
}
inline void split(int x,int y){makeroot(x),access(y),splay(y);}
inline void link(int x,int y){makeroot(x);if(find(y)!=x) fa(x)=y;}
inline void cut(int x,int y){makeroot(x);if(find(y)==x&&fa(y)==x&&!ls(y)) fa(y)=rs(x)=0;}
inline int query(int x,int y){
if(find(x)!=find(y)) return -1;
split(x,y);
return tr[y].siz;
}
inline void dfs(int u){
if(!u) return;down(u);
dfs(ls(u));
stk[++top]=u;
dfs(rs(u));
}
inline void link1(int x,int y){
if(find(x)==find(y)){
split(x,y);
if(tr[y].siz<=2) return;
top=0;dfs(y);++tot;
for(re int i=1;i<top;++i) cut(stk[i],stk[i+1]);
for(re int i=1;i<=top;++i) link(stk[i],tot);
}
else link(x,y);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;LCT1::tot=LCT2::tot=n;
int op,x,y,ans;
while(m--){
cin>>op>>x>>y;x^=lastans,y^=lastans;
if(op==1) LCT1::link1(x,y),LCT2::link1(x,y);
if(op==2) ans=LCT1::query(x,y),lastans=(ans==-1?lastans:ans),cout<<ans<<'\n';
if(op==3) ans=LCT2::query(x,y),lastans=(ans==-1?lastans:ans),cout<<ans<<'\n';
}
return 0;
}

[APIO2018] 铁人两项

关于点双有一个很好的性质:对于一个点双中的两点,它们之间简单路径的并集,恰好完全等于这个点双。

证明略,因为不会。

那么放到圆方树上,就有结论:两圆点在圆方树上的路径,与路径上经过的方点相邻的圆点的集合,就等于原图中两点简单路径上的点集。

那么在本题中,如果固定 ,那么合法的 的个数就等于 之间简单路径的并集的点数减 2(去掉 本身)。

那么,我们给方点赋上对应点双大小的权值,给圆点赋上 -1 的权值。

答案就是任意两个圆点之间路径的权值和。

套路的,考虑每个点被计算的次数。

考虑每个点 作为 时的贡献。

都在其子树内,则贡献为

只有一个在其子树内,则贡献为

其中 为当前连通块内圆点个数。

乘 2 是因为题目要求统计有序对。

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
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=1e5+10,maxm=2e5+10;
int n,m,cnt,top,tim,tot,ans,now;
int head[maxn];
int dfn[maxn],low[maxn];
int stk[maxn];
int siz[maxn<<1],w[maxn<<1];
vector<int> g[maxn<<1];
struct edge{
int to,nxt;
}e[maxm<<1];
inline void add(int u,int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
inline void tarjan(int u,int fa){
++now;
dfn[u]=low[u]=++tim;
stk[++top]=u;w[u]=-1;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
g[++tot].push_back(u),g[u].push_back(tot),++w[tot];
int x;
do{
x=stk[top--];
g[tot].push_back(x),g[x].push_back(tot),++w[tot];
}while(x!=v);
}
}
else if(v!=fa) low[u]=min(low[u],dfn[v]);
}
}
inline void dfs(int u,int fa){
siz[u]=(u<=n);
for(auto v:g[u]){
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
ans+=2ll*(siz[u]-siz[v])*siz[v]*w[u];
}
ans+=2ll*siz[u]*(now-siz[u])*w[u];
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;tot=n;
for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u);
for(re int i=1;i<=n;++i) if(!dfn[i]) now=0,tarjan(i,i),dfs(i,0);
cout<<ans;
return 0;
}

Tourists

如果不带修,那么给方点赋上对应点双内所有点的权值最小值,圆点赋对应点权值,查询就是链上最小值。

然后考虑带修。一个想法是给每个方点维护一个 multiset,修改时要修改对应点点权和相连方点点权。

但这样做给个菊花就卡死了。

不过我们可以这样做:令每个方点的权值为其儿子中的最小权值,且 multiset 只维护其儿子的点权。

这样修改时只需要额外修改父亲即可。

询问时正常询问,但如果询问的两个点的 lca 是一个方点,那还要额外统计 lca 的父亲的贡献。

卡常,需要加快读。

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
#include"bits/stdc++.h"
#define re register
#define int long long
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
using namespace std;
const int maxn=5e5+10,inf=1e18;
int n,m,Q,cnt,tim,top,tot,rt,segcnt,id;
int a[maxn],head[maxn];
int dfn[maxn],low[maxn],stk[maxn];
vector<int> g[maxn<<1];
multiset<int> s[maxn];
struct edge{
int to,nxt;
}e[maxn<<1];
struct node{
int fa,dep,siz,hson,top,dfn,seq;
}t[maxn<<1];
struct tree{
int ls,rs,mn;
}tr[maxn<<2];
inline void add(int u,int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
inline void tarjan(int u,int fa){
dfn[u]=low[u]=++tim;
stk[++top]=u;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
g[++tot].push_back(u),g[u].push_back(tot);
int x;
do{
x=stk[top--];
g[tot].push_back(x),g[x].push_back(tot);
}while(x!=v);
}
}
else if(v!=fa) low[u]=min(low[u],dfn[v]);
}
}
inline void dfs1(int u,int fa){
t[u].fa=fa;t[u].siz=1;t[u].dep=t[fa].dep+1;
for(auto v:g[u]){
if(v==fa) continue;
dfs1(v,u);
t[u].siz+=t[v].siz;
if(!t[u].hson||t[v].siz>t[t[u].hson].siz) t[u].hson=v;
}
}
inline void dfs2(int u,int top){
t[u].dfn=++id;t[id].seq=u;t[u].top=top;
if(!t[u].hson) return;
dfs2(t[u].hson,top);
for(auto v:g[u]){
if(v==t[u].hson||v==t[u].fa) continue;
dfs2(v,v);
}
}
inline void up(int p){tr[p].mn=min(tr[ls(p)].mn,tr[rs(p)].mn);}
inline void build(int l,int r,int &p){
if(!p) p=++segcnt;
if(l==r){tr[p].mn=a[t[l].seq];return;}
int mid=(l+r)>>1;
build(l,mid,ls(p)),build(mid+1,r,rs(p));
up(p);
}
inline void update(int l,int r,int pos,int val,int p){
if(l==r){tr[p].mn=val;return;}
int mid=(l+r)>>1;
if(pos<=mid) update(l,mid,pos,val,ls(p));
else update(mid+1,r,pos,val,rs(p));
up(p);
}
inline int query(int l,int r,int L,int R,int p){
if(l>=L&&r<=R) return tr[p].mn;
int mid=(l+r)>>1,res=inf;
if(L<=mid) res=min(res,query(l,mid,L,R,ls(p)));
if(R>mid) res=min(res,query(mid+1,r,L,R,rs(p)));
return res;
}
inline int query(int u,int v){
int res=inf;
while(t[u].top!=t[v].top){
if(t[t[u].top].dep<t[t[v].top].dep) swap(u,v);
res=min(res,query(1,tot,t[t[u].top].dfn,t[u].dfn,1));
u=t[t[u].top].fa;
}
if(t[u].dep>t[v].dep) swap(u,v);
res=min(res,query(1,tot,t[u].dfn,t[v].dfn,1));
if(u>n) res=min(res,a[t[u].fa]);
return res;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m>>Q;tot=n;
for(re int i=1;i<=n;++i) cin>>a[i];
for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u);
tarjan(1,0),dfs1(1,0),dfs2(1,1);
for(re int i=2;i<=n;++i) s[t[i].fa-n].insert(a[i]);
for(re int i=n+1;i<=tot;++i) a[i]=s[i-n].empty()?inf:*s[i-n].begin();
build(1,tot,rt);
char op;int x,y;
while(Q--){
cin>>op>>x>>y;
if(op=='C'){
update(1,tot,t[x].dfn,y,1);
if(x==1){a[x]=y;continue;}
s[t[x].fa-n].erase(s[t[x].fa-n].lower_bound(a[x]));
s[t[x].fa-n].insert(y);
int mn=*s[t[x].fa-n].begin();
if(mn==a[t[x].fa]){a[x]=y;continue;}
update(1,tot,t[t[x].fa].dfn,mn,1);
a[t[x].fa]=mn,a[x]=y;
}
else cout<<query(x,y)<<'\n';
}
return 0;
}

【模板】静态仙人掌

双倍经验: Freda的传呼机

不会,摆。

【MX-S1-T3】电动力学

困难 dp,摆。

「SWTR-8」地地铁铁

困难性质,摆。