先說一個簡單問題:給定一個 \(n\times m\) 的黑白網格圖,每次可以將一行或者一列染成同一種色,判斷是否能到達?
經典做法:倒過來考慮,每次將顏色全相同或為 *
的一行全染成 *
,判斷是否可以將這張圖染成全 *
。經典網格圖轉二分圖,如果 \(s_{i,j}='W'\) 則將 \(i\) 向 \(j'\) 連一條有向邊,否則 \(j'\) 向 \(i\) 連。每次相當於將一個沒有出度或入度的點對應的邊都刪掉,判斷是否能把所有邊刪空。容易發現等價於判斷是否存在強連通分量,如果是 DAG,則直接按拓撲序取即可,否則隨便找一個 SCC,裡面點永遠動不了。
不過這個題有額外操作方法,保證了每個 SCC 都也能操作成功,那麼這個代價怎麼算呢?
upd:不是哥們我讀錯題了,,,當時這裡卡住了以為是個很難的問題,現在發現,它的題意指的是,同時染的格子的顏色可以從這次染這行的和這列的裡面隨便選一個,不過不能憑空製造!因此本題應該換個角度這樣理解:咕咕咕
現在有了這個結論,那麼我們只需要求出每個時刻的 SCC 狀態即可!!!
受到一些可能是 QOJ 上的經典題的啟發,我們想到了分治!不過類似連通性這種直接做分治,會遇到一些很大的問題。我比如把 \(time\leq mid\) 的所有邊拿出來跑 SCC,那麼現在會產生一個縮點之後是 DAG 的有向圖,可是我這些 DAG 邊究竟應該放哪裡呢?我之後做右半邊的時候點是用縮完之後還是之前的呢???
所以接下來我們就要明確一下這個常見套路。我們對每條邊求出的是,它在第幾時刻之後"完成使命"了,也就是說,相連的兩個點歸到了同一個 SCC。這樣,我最後求每個時刻的 SCC 時,只要把掛在這個時刻上的所有邊拉出來,縮個並查集就好了。因此,我們可以用 solve(l,r,vector<int>g)
來表示一次分治,要去將 \(g\) 裡面所有邊得到它們對應的時刻。當然,有可能完成不了使命,我們就將其設為 \(q+1\),不是大問題。當然,每條邊的完成時刻一定不早於出現時刻。所以何時加什麼邊就很明確了:
先得到一個 \(mid\)。將 \(g\) 中所有 \(time\leq mid\) 的邊拉出來跑 SCC,得到一堆強連通塊,這樣可以求出 \(g\) 中每條邊應該歸到左,還是右,亦或直接扔掉。具體地,對一個 \(\leq mid\) 的邊,如果兩點已經屬於一個 SCC 了,就歸到左邊,否則歸到右邊;\(>mid\) 的邊,如果兩點已經屬於了,就沒用可以扔了,否則歸到右邊,接下來繼續遞迴即可。
看起來很對,不過寫程式碼的時候會產生這樣一個問題:用縮前還是縮後的點來著?其實很容易想到肯定是用縮後的點的,不過加入的時間要注意一下,應該是先遞迴處理左半邊,然後把這次的 SCC 縮點做好(可以再用個並查集維護),最後處理右半邊。不難做到 \(O(n+q\log q)\)。如果實現地不精細多個 log 也無所謂,只要不寫 set 常數應該還好。
程式碼:
#include<bits/stdc++.h>
using namespace std;
int uu[200005],vv[200005],cx[200005],N;
vector<int>g[400005];
int dfn[400005],low[400005],st[400005],vist[400005];
int col[400005],t1=0,t2=0;
void tarjan(int x){
dfn[x]=low[x]=++t1;
st[++t2]=x,vist[x]=1;
for(auto cu:g[x]){
if(!dfn[cu]){
tarjan(cu);
low[x]=min(low[x],low[cu]);
}else if(vist[cu]){
low[x]=min(low[x],dfn[cu]);
}
}
if(dfn[x]!=low[x])return;
int pp;
do{
pp=st[t2--];vist[pp]=0;
col[pp]=x;
}while(pp!=x);
}
int ff[400005];
int findff(int x){
return x==ff[x]?x:ff[x]=findff(ff[x]);
}
void solve(int l,int r,vector<int>gg){
if(l==r){
for(auto d:gg)cx[d]=l;
return;
}
int mid=(l+r)>>1;
vector<int>se;
for(auto d:gg){
int U=findff(uu[d]),V=findff(vv[d]);
dfn[U]=low[U]=col[U]=vist[U]=0;
dfn[V]=low[V]=col[V]=vist[V]=0;
if(d<=mid){
se.emplace_back(U);se.emplace_back(V);
g[U].emplace_back(V);
}
}
sort(se.begin(),se.end());
se.resize(unique(se.begin(),se.end())-se.begin());
t1=t2=0;
for(auto x:se)if(!dfn[x]){
tarjan(x);
}
vector<int>g1,g2;
for(auto d:gg){
int U=ff[uu[d]],V=ff[vv[d]];
if(d<=mid){
if(col[U]==col[V])g1.emplace_back(d);
else g2.emplace_back(d);
}else{
if(col[U]!=col[V]||!col[U]||!col[V])g2.emplace_back(d);
}
}
vector<pair<int,int>>v;
for(auto x:se){
g[x].clear();
v.emplace_back(x,col[x]);
}
solve(l,mid,g1);
for(auto pi:v){
int fx=findff(pi.first),fy=findff(pi.second);
if(fx!=fy)ff[fx]=fy;
}
solve(mid+1,r,g2);
}
int fa[400005],cnt[400005];
int findfather(int x){
return x==fa[x]?x:fa[x]=findfather(fa[x]);
}
vector<int>v2[200005];
long long f(int x){
return x==1?0:1ll*x*x;
}
int main(){
int n,m,q;
scanf("%d%d%d",&n,&m,&q);N=n+m;
for(int i=1;i<=q;++i){
int u,v;char op[15];
scanf("%d%d%s",&u,&v,op+1);
int U=u,V=n+v;
if(op[1]=='B')swap(U,V);
uu[i]=U,vv[i]=V;
}
vector<int>vc;
for(int i=1;i<=q;++i)vc.emplace_back(i);
for(int i=1;i<=N;++i)ff[i]=i;
solve(1,q+1,vc);
for(int i=1;i<=N;++i)fa[i]=i,cnt[i]=1;
for(int i=1;i<=q;++i){
if(cx[i]&&cx[i]<=q)v2[cx[i]].emplace_back(i);
}
long long ans=0;
for(int i=1;i<=q;++i){
for(auto d:v2[i]){
int fu=findfather(uu[d]),fv=findfather(vv[d]);
if(fu!=fv){
fa[fu]=fv;
ans=ans-f(cnt[fu])-f(cnt[fv]);
ans=ans+f(cnt[fv]+=cnt[fu]);
}
}
printf("%lld\n",ans);
}
return 0;
}