[Tricks-00003]CF1989F 套路疊加,高階分治

maihe發表於2024-11-16

先說一個簡單問題:給定一個 \(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;
}

相關文章