7.20学习日记:圆方树
7.20 学习日记 : 圆方树
前置芝士:
学习圆方树首先就要学习tarjan部分中的点双连通分量和割点等内容
割点:对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。(选自oi-wiki)
说人话呢就是说你把这个点和他的连边删掉,这个大的连通就会被分成很多小的连通图
点双连通分量:图中任意两不同点之间都有至少两条点不重复的路径。
点不重复既指路径上点不重复(简单路径),也指两条路径的交集为空(当然,路径必然都经过出发点和到达点,这不在考虑范围内)。(依旧选自oi-wiki)
我来解释!
就是图中随便选两个点,他们之间的两条简单路径互不相交,同时也意味着你随便删一个点这个图还都是连通的,这个和后面的有关
如果大家还不了解可以自行学习tarjan
来到主角:圆方树
构造过程:
圆方树 顾名思义,就是由圆点和方点组成 ,每当我们找到一个点双连通分量时(大概可以理解成一个环), 我们将这个连通块抽象成一个方点,然后将分量里的其他点作为圆点连在方点上,其次将方点连到割点上,就建成新树啦。
求割点当然需要用tarjan算法啦
luogu P4630 [APIO2018] 铁人两项
简化题意:给定一张简单无向图,问有多少对三元组 (s ,c , f)(s, c, f 互不相同)使得存在一条简单路径从 s 出发,经过 c 到达 f。
给一个圆方树题目的小trick:给点附上合适的权值
引理:对于一个点双中的两点,它们之间简单路径的并集,恰好完全等于这个点双。(需要用网络流证明,但是我不会,等我学会了我会回来更新的)
相当于考虑两圆点在圆方树上的路径,与路径上经过的方点相邻的圆点的集合,就等于原图中两点简单路径上的点集。
我们固定s , f考虑c
此时这个c就是s到f简单路径的点集
如果暴力枚举太大了咋办?
所以我们考虑给方点赋点权值为整个点双的大小,圆点为-1,这样路径上的和就是点集的个数
但是我们不知道s,f啊??
完单了吗?
并没有
我们可以借鉴树上dp , 只计算他对答案的贡献即可
贴代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 20 ;
int n , dfn[N] , tot , m , fr , w[N] , to , cnt , sta[N] , low[N] , tp ;
vector<int>G[N] , T[N];
typedef long long ll ;
ll ans , num ;
void tarjan(int u , int f){
low[u] = dfn[u] = ++tot;
sta[++tp] = u ;
++num;
for(auto v : G[u]){
// if( v != f ){
if( !dfn[v] ){
tarjan( v , u ) ;
low[u] = min( low[v] , low[u]);
if( low[v] == dfn[u] ){
w[++cnt] = 0;
for (int x = 0; x != v; --tp) {
x = sta[tp];
T[cnt].push_back(x);
T[x].push_back(cnt);
++w[cnt];
}
T[cnt].push_back(u);
T[u].push_back(cnt);
++w[cnt];
}
}
else{
low[u] = min( low[u] , dfn[v]);
}
}
}
int sz[N] ;
void dfs(int u , int f){
sz[u] = ( u <= n ) ;
for(auto v : T[u]){
if( v != f){
dfs( v , u ) ;
ans += 2ll * sz[v] * sz[u] * w[u];
sz[u] += sz[v];
}
}
ans += 2ll * w[u] * sz[u] * ( num - sz[u] );
}
int main(){
cin >> n >> m ;
for(int i = 1 ; i <= m ;++i){
cin >> fr >> to ;
G[fr].push_back(to);
G[to].push_back(fr);
}
for(int i = 1 ; i <= n ;i++ ){
w[i] = -1 ;
}
cnt = n ;
for(int i = 1 ; i <= n ; i++ ){
if(dfn[i] == 0 ){
num = 0 ;
tarjan( i , 0 ) ;
--tp ;
dfs( i , 0 ) ;
}
}
cout << ans << endl ;
return 0;
}
警示后人!!!!
w[++cnt] = 0 ;
while(sta[tp] != u){
T[cnt].push_back(sta[tp]);
T[sta[tp]].push_back(cnt);
w[cnt]++;
tp-- ;
}
这样退栈是错误的,为什么呢?
因为他可能会退到其他的连通分量里
正确做法
w[++cnt] = 0;
for (int x = 0; x != v; --tp) {
x = sta[tp];
T[cnt].push_back(x);
T[x].push_back(cnt);
++w[cnt];
}
68 -> 100 pts
Codeforces #487 E. Tourists
题意:
给定一张简单无向连通图,要求支持两种操作:
1.修改一个点的点权。
2.询问两点之间所有简单路径上点权的最小值。
这题挺水的,树剖+圆方树板子,操作1直接单点修改不用说 , 操作2我们直接对于每一个方点维护一个set最小值,将最小值附在方点上, 此时只需要查询方点就可以了。每次有新输入的时候直接更新。(此T我直接二十分钟写完一发直接AC)
#include<bits/stdc++.h>
using namespace std;
#define lid id << 1
#define rid (id << 1) + 1
const int N = 3e5 + 20 ;
vector<int>G[N] , T[N] ;
set<int>Z[N];
struct tree{
int l , r , minn ;
}tr[N << 4];
int dfc[N] , tot , low[N] , num , js , sta[N] , n , m , q , fr , to , tp , w[N];
void tarjan(int u){
low[u] = dfc[u] =++tot ;
sta[++tp] = u ;
for(auto v : G[u]){
if(!dfc[v]){
tarjan(v);
low[u] = min(low[u] , low[v]);
if( dfc[u] == low[v] ){
++js;
for(int x = 0 ; x != v ; --tp){
x = sta[tp];
T[x].push_back(js);
T[js].push_back(x);
}
T[u].push_back(js);
T[js].push_back(u);
}
}
else{
low[u] = min( low[u] , dfc[v] );
}
}
}
int dep[N] , rnk[N] , cnt , son[N] , dfn[N] , fa[N] , top[N] , sz[N] ;
void update(int id){
tr[id].minn = min( tr[lid].minn , tr[rid].minn ) ;
}
void build(int id , int l , int r){
tr[id].l = l , tr[id].r = r;
if( l == r ){
tr[id].minn = w[rnk[l]];
// cout << l << " " << tr[id].minn << endl ;
return ;
}
int mid = ( l + r ) >> 1 ;
build( lid , l , mid ) ;
build( rid , mid + 1 , r );
update(id);
}
void modify( int id , int x , int val ){
if( tr[id].l == tr[id].r and tr[id].r == x ){
tr[id].minn = val ;
return ;
}
int mid = ( tr[id].l + tr[id].r) >> 1 ;
if( x <= mid ){
modify( lid , x , val ) ;
}
else{
modify( rid, x , val ) ;
}
update(id);
}
int query( int id , int l , int r ){
if( l <= tr[id].l and tr[id].r <= r ){
return tr[id].minn ;
}
// cerr << l << " " << r << endl ;
int mid = ( tr[id].l + tr[id].r ) >> 1 ;
int res = 0x3f3f3f3f;
if( l <= mid ){
res = min( res , query( lid , l , r )) ;
}
if( r >= mid + 1 ){
res = min( res , query( rid, l , r ) );
}
return res ;
}
void dfs1( int u , int f ){
dep[u] = dep[f] + 1 ;
fa[u] = f ;
sz[u] = 1 ;
for(auto v : T[u]){
if( v != f ){
dfs1( v , u ) ;
if(sz[son[u]] < sz[v])son[u] = v ;
sz[u] += sz[v] ;
}
}
}
void dfs2(int u , int t ){
top[u] = t ;
dfn[u] = ++cnt ;
rnk[cnt] = u ;
if(!son[u])return ;
dfs2( son[u] , t ) ;
for(auto v : T[u]){
if(v != fa[u]){
if(v != son[u])dfs2( v , v ) ;
}
}
}
int gans( int x , int y ){
int res = 0x3f3f3f3f ;
while( top[x] != top[y] ){
if(dep[top[x]] < dep[top[y]])swap(x , y);
res = min( res , query( 1 , dfn[top[x]] , dfn[x]));
x = fa[top[x]] ;
}
if( dep[x] > dep[y])swap(x , y) ;
res = min( res , query( 1 , dfn[x] , dfn[y])) ;
return res ;
}
int lca( int x , int y){
while( top[x] != top[y] ){
if(dep[top[x]] < dep[top[y]])swap(x , y);
x = fa[top[x]] ;
}
if( dep[x] > dep[y])swap(x , y) ;
return x ;
}
char op ;
int main(){
cin >> n >> m >> q ;
for (int i = 1; i <= n; ++i) cin >> w[i];
js = n;
for (int i = 1; i <= m; ++i) {
cin >> fr >> to;
G[fr].push_back(to);
G[to].push_back(fr);
}
tarjan(1);
dfs1(1, 0);
// cerr << "awawa" << endl ;
dfs2(1 ,1);
for (int i = 1; i <= n; ++i)if (fa[i]) Z[fa[i]].insert(w[i]);
for (int i = n + 1; i <= js; ++i) w[i] = *Z[i].begin();
build( 1 , 1 , js ) ;
for(int i = 1 ; i <= q ; i++ ){
cin >> op ;
int a , w1 ;
if (op == 'C') {
cin >> a >> w1 ;
modify(1, dfn[a], w1);
if (fa[a]) {
int u = fa[a];
Z[u].erase(Z[u].lower_bound(w[a]));
Z[u].insert(w1);
if(w[u] != *Z[u].begin()) {
w[u] = *Z[u].begin();
modify(1, dfn[u], w[u]);
}
}
w[a] = w1 ;
}
else{
cin >> a >> w1 ;
int ans = gans( a , w1 ) ;
// cout << ans << endl ;
if( lca(a , w1) > n){
ans = min( ans , w[fa[lca(a,w1)]]) ;
}
cout << ans << endl ;
}
}
return 0 ;
}
luogu P4606 [SDOI2018] 战略游戏
题意简介:给出一个简单无向连通图。有 q 次询问:
每次给出一个点集 S(2<=|S|<=n),问有多少个点 u 满足 u 属于 S 且删掉 u 之后 S 中的点不全在一个连通分量中。
其实就是求这个点集里面有多少个割点
我们怎么求呢?
将圆点与方点之间边的权值设为1,此时两个点之间的圆点个数等于两个点之间的距离
把 S 中的点按照 DFS 序排序,计算排序后相邻两点的距离和(还包括首尾两点之间的距离),答案就是距离和的一半,因为每条边只被经过两次。
当节点最浅的那个点是圆点的时候答案需要+1
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 20 ;
int t , q , qs , n , m , fr , to ,dfn[N] , tot , low[N] , cnt , sta[N] , tp ;
vector<int>G[N],T[N];
void tarjan(int u){
dfn[u] = low[u] = ++tot ;
// ;
sta[++tp] = u ;
for(auto v : G[u]){
// cout << v << endl ;
if(!dfn[v]){
tarjan(v);
low[u] = min( low[u] , low[v] );
if( low[v] == dfn[u] ){
cnt++;
for(int x = 0 ; x != v ; tp--){
x = sta[tp];
T[x].push_back(cnt);
T[cnt].push_back(x);
}
T[u].push_back(cnt);
T[cnt].push_back(u);
}
}
else{
low[u] = min( low[u] , dfn[v] ) ;
}
}
}
int dis[N] , fa[N] , dep[N] , top[N] , son[N] , dfn1[N] , sz[N];
void dfs1(int u , int f){
fa[u] = f ;
dep[u] = dep[f] + 1 ;
sz[u] = 1 ;
for(auto v : T[u]){
if( v != f ){
dfs1( v , u ) ;
sz[u] += sz[v] ;
if( sz[son[u]] < sz[v])son[u] = v ;
}
}
}
int num ;
void dfs2( int u , int t ){
dfn1[u] = ++num ;
top[u] = t ;
dis[u] = dis[fa[u]] + ( u <= n );
if(!son[u])return;
dfs2( son[u] , t ) ;
for(auto v : T[u]){
if( v != fa[u] and v != son[u] ){
dfs2( v , v ) ;
}
}
}
int lca( int a , int b ){
while(top[a]!=top[b]){
if( dep[top[a]] < dep[top[b]])swap(a , b) ;
a = fa[top[a]] ;
}
if( dep[a] > dep[b] )swap(a , b);
return a ;
}
struct dfns{
int id , dfnid ;
}S[N] ;
bool cmp( dfns a1 , dfns a2){
return a1.dfnid < a2.dfnid ;
}
int dfc;
int dfn2[N] ;
void DFS(int u, int fz) {
dfn2[u] = ++dfc;
for (int v : T[u])
if (v != fz) DFS(v, u);
}
int main(){
// freopen("data.in","r",stdin);
// freopen("data.out","w", stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> t ;
while(t--){
tot = tp = num = 0 ;
cin >> n >> m ;
cnt = n ;
for(int i = 1 ; i <= max(n , m) * 2;i ++){
G[i].clear();
T[i].clear();
dfn2[i] = dfn1[i] = dis[i] = fa[i] = dep[i] = top[i] = son[i] = sz[i] = low[i] = dfn[i] = sta[i] = 0 ;
}
for(int i = 1 ; i <= m ; i++){
cin >> fr >> to ;
G[fr].push_back(to);
G[to].push_back(fr);
}
tarjan(1);
dfs1( 1 , 0 );
dfs2( 1 , 1 );
DFS(1 , 0);
cin >> q ;
for(int i = 1 ; i <= q ; i++ ){
cin >> qs ;
for(int j = 1 ; j <= qs ; j++){
cin >> S[j].id ;
S[j].dfnid = dfn2[S[j].id];
}
sort( S + 1 , S + 1 + qs , cmp );
int ans = 0 ;
S[qs + 1] = S[1];
for(int j = 1 ; j <= qs ; j++ ){
ans += ( dis[S[j].id] + dis[S[j+1].id] - 2 * dis[lca(S[j].id , S[j + 1].id)]);
}
if(lca(S[1].id, S[qs].id) <= n) ans += 2;
ans = ans - 2 * qs ;
ans /= 2 ;
cout << ans << endl ;
for(int j = 1 ; j <= qs ; j++)S[j].id = S[j].dfnid = 0 ;
}
}
return 0;
}
警示后人
圆方树一定要开二倍,不然会Mle

浙公网安备 33010602011771号