CF487E Tourists + 圓方樹學習筆記(圓方樹+樹剖+線段樹+multiset)
QWQ果然我已經什麼都學不會的人了。
這個題目要求的是圖上所有路徑的點權和!QWQ(我只會樹上啊!)
這個如果是好啊
這時候就需要
圓方樹!
首先在介紹圓方樹之前,我們先來一點簡單的前置知識
首先,我們需要知道什麼是
點雙聯通分量
若一個無向圖中的去掉任意一個節點都不會改變此圖的連通性,即不存在割點,則稱作點雙連通圖。那麼一個極大的點雙聯通子圖,就是一個雙聯通分量了
那麼求這個方法,和普通求割點的\(tarjan\)類似
用一個棧維護所有的點
對於搜尋到一個割點,然後把他的棧內部的點依次彈棧,直到這條邊的\(to\)被彈出來為止(這裡不能直接把當前彈出去的原因是一個點可能屬於好幾個點雙聯通,所以如果此時把這個點彈出去,是錯誤的
程式碼就直接搬了圓方樹裡面的,請手動忽略\(addedge\)
void tarjan(int x,int faa) { dfn[x]=low[x]=++tot; st[++ttop]=x; for (int i=point1[x];i;i=nxt1[i]) { int p = to1[i]; if (p==faa) continue; if (!dfn[p]) { tarjan(p,x); low[x]=min(low[x],low[p]); if (low[p]>=dfn[x]) //如果這個點是割點,就把當前點雙的所有點出棧,建成圓方樹 { ++num; addedge(num,x); addedge(x,num); do { addedge(st[ttop],num); addedge(num,st[ttop]); ttop--; }while (st[ttop+1]!=p); } } else low[x]=min(low[x],dfn[p]); } }
QWQ那麼圓方樹是怎麼一個東西呢。
實際上圓方樹就是把圖中所有的點雙連通分量找出來,然後建一個新的方點
和所有的點雙裡面的點連邊,然後把原圖對應點雙裡面的邊都刪除(相當於建了一個新圖)
然後新建的點是方點,原圖的點是原點
這裡直接引用了貓琨的圖qwq
那麼經過這麼一番處理,我們會發現(詳細證明請看WC的ppt)
這個由圓點和方點組成的新圖是一顆樹
叫做
圓方樹
那麼圓方樹有什麼性質呢?
性質
1任意兩個圓點(或方點)不會相鄰。
2.如果兩個方點樹上距離是那麼一定有一個圓點連線他們,而且這個圓點是兩個方點對應點雙的公共點。
3,對於兩個圓點 \(a,b\)之間的樹上路徑,
(1)路徑上的所有圓點都是割點!
(2)\(a,b\)所有簡單路徑的並 是路徑上所有方點對應的點雙(包括\(a=b\)的情況)
QWQ然後我們就能夠將大部分圖上問題直接轉化成樹上問題了
那麼對於這個題。
首先,詢問路徑的上的簡單路徑的點權\(min\),我們可以將方點的權值設為所連圓點的權值\(min\)(這裡要開一個\(set)\),然後圓點的點權弄成原來的權值,對於每次詢問,我們直接回答鏈\(min\)就行,這個可以用線段樹+樹剖來實現。
那麼修改呢
QWQ
由於我們考慮到,一個圓點的點權修改,最多會影響到\(O(n)\)個級別的方點(一個點可以屬於好幾個點雙)。
QWQ
那麼該如何是好呢?
我們考慮對於每個方點,維護除去他的根(建出來樹先dfs一下)的所有圓點的權值的\(min\)(由於涉及到刪除什麼的,所以要用\(multiset\))
那麼這樣每次修改,我們只需要修改對應圓點的\(fa\)的\(set\)就ok了
有一個要注意的地方就是!
如果我們對於一次詢問的\(lca\),假設他的方點的話,我們還要考慮他的\(fa\)的\(val\)大小,因為我們只詢問路徑上的方點,不會計算這個點的。
總的來說,一般圓方樹的題都要很好的運用圓方樹的性質的
(雖然我根本想不到圓方樹)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 2e5+5;
const int maxm = 2*maxn;
int point[maxn],nxt[maxm],to[maxm];
int point1[maxn],nxt1[maxm],to1[maxm];
int cnt1,cnt;
int n,m,k,q;
int deep[maxn];
int top[maxn],size[maxn],son[maxn];
int fa[maxn];
int f[4*maxn];
int add[4*maxn];
multiset<int> s[maxn];
int val[maxn];
int tot;
int low[maxn],dfn[maxn];
int newnum[maxn],newval[maxn];
int st[maxn],ttop;
int num,tmp;
void addedge(int x,int y)
{
// cout<<x<<" "<<y<<" *"<<endl;
nxt[++cnt]=point[x];
to[cnt]=y;
point[x]=cnt;
}
void addedge1(int x,int y)
{
nxt1[++cnt1]=point1[x];
to1[cnt1]=y;
point1[x]=cnt1;
}
void tarjan(int x,int faa)
{
dfn[x]=low[x]=++tot;
st[++ttop]=x;
for (int i=point1[x];i;i=nxt1[i])
{
int p = to1[i];
if (p==faa) continue;
if (!dfn[p])
{
tarjan(p,x);
low[x]=min(low[x],low[p]);
if (low[p]>=dfn[x]) //如果這個點是割點,就把當前點雙的所有點出棧,建成圓方樹
{
++num;
addedge(num,x);
addedge(x,num);
do
{
addedge(st[ttop],num);
addedge(num,st[ttop]);
ttop--;
}while (st[ttop+1]!=p);
}
}
else
low[x]=min(low[x],dfn[p]);
}
}
void dfs1(int x,int faa,int dep)
{
deep[x]=dep;
size[x]=1;
int maxson=-1;
for (int i=point[x];i;i=nxt[i])
{
int p =to[i];
if (p==faa) continue;
fa[p]=x;
dfs1(p,x,dep+1);
if (size[p]>maxson)
{
maxson=size[p];
son[x]=p;
}
size[x]+=size[p];
}
}
void dfs2(int x,int chain)
{
newnum[x]=++tmp;
newval[tmp]=val[x];
top[x]=chain;
if (!son[x]) return;
dfs2(son[x],chain);
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (!newnum[p])
{
dfs2(p,p);
}
}
}
void up(int root)
{
f[root]=min(f[2*root],f[2*root+1]);
}
void build(int root,int l,int r)
{
if (l==r)
{
f[root]=newval[l];
return;
}
int mid =l + r >> 1;
build(2*root,l,mid);
build(2*root+1,mid+1,r);
up(root);
}
void update(int root,int l,int r,int x,int p)
{
if (l==r)
{
f[root]=p;
return;
}
int mid = l+r >> 1;
if (x<=mid) update(2*root,l,mid,x,p);
if (x>mid) update(2*root+1,mid+1,r,x,p);
up(root);
}
int query(int root,int l,int r,int x,int y)
{
if (x<=l && r<=y) return f[root];
int mid = l+r >> 1;
int ans=2e9;
if (x<=mid) ans=min(ans,query(2*root,l,mid,x,y));
if (y>mid) ans=min(ans,query(2*root+1,mid+1,r,x,y));
return ans;
}
int treesum(int x,int y)
{
int ans=2e9;
int xx=x,yy=y;
while (top[x]!=top[y])
{
if (deep[top[x]]<deep[top[y]]) swap(x,y);
//cout<<1<<endl;
ans=min(ans,query(1,1,num,newnum[top[x]],newnum[x]));
x=fa[top[x]];
//cout<<"***"<<endl;
}
// cout<<1<<endl;
//cout<<x<<" "<<y<<endl;
if (deep[x]>deep[y]) swap(x,y);
ans=min(ans,query(1,1,num,newnum[x],newnum[y]));
int l = x;
if (l>n) ans=min(ans,val[fa[l]]);
return ans;
}
int main()
{
n=read(),m=read(),q=read();
tmp=0;
num=n;
for (int i=1;i<=n;i++) val[i]=read();
for (int i=1;i<=m;i++)
{
int x=read(),y=read();
addedge1(x,y);
addedge1(y,x);
}
for (int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i,0);
}
dfs1(1,0,1);
for (int i=n+1;i<=num;i++)
{
for (int j=point[i];j;j=nxt[j])
{
int p = to[j];
if (p==fa[i]) continue;
s[i].insert(val[p]);
}
if (s[i].size()==0) val[i]=2e9;
else val[i]=*(s[i].begin());
}
dfs2(1,1);
build(1,1,num);
for (int i=1;i<=q;i++)
{
char ss[10];
scanf("%s",ss+1);
if (ss[1]=='C')
{
int x=read(),y=read();
update(1,1,num,newnum[x],y);
if (fa[x]==0)
{
val[x]=y;
continue;
}
int now = *(s[fa[x]].begin());
s[fa[x]].erase(s[fa[x]].find(val[x]));
s[fa[x]].insert(y);
int tmp1 = (*(s[fa[x]].begin()));
if (tmp1!=now)
{
update(1,1,num,newnum[fa[x]],tmp1);
val[fa[x]]=tmp1;
}
val[x]=y;
}
else
{
int x=read(),y=read();
cout<<treesum(x,y)<<"\n";
}
}
return 0;
}