1. 程式人生 > 實用技巧 >學長小清新題表之UOJ 14.DZY Loves Graph

學長小清新題表之UOJ 14.DZY Loves Graph

學長小清新題表之UOJ 14.DZY Loves Graph

題目描述

\(DZY\)開始有 \(n\) 個點,現在他對這 \(n\) 個點進行了 \(m\) 次操作,對於第 \(i\) 個操作(從 \(1\)開始編號)有可能的三種情況:

\(Add\ a\ b:\) 表示在 \(a\)\(b\) 之間連了一條長度為 \(i\)的邊(注意,\(i\)是操作編號)。保證 \(1≤a,b≤n\)

\(Delete\ k:\) 表示刪除了當前圖中邊權最大的\(k\)條邊。保證$ k$一定不會比當前圖中邊的條數多。

\(Return\): 表示撤銷第 \(i−1\)次操作。保證第 \(1\) 次操作不是 \(Return\)

且第 \(i−1\)次不是 Return 操作。

請你在每次操作後告訴\(DZY\)當前圖的最小生成樹邊權和。如果最小生成樹不存在則輸出 \(0\)

輸入格式

第一行兩個正整數 \(n,m\)。表示有 \(n\) 個點 $m $個操作。 接下來 \(m\)行每行描述一個操作。

輸出格式

對於每一個操作輸出一行一個整數表示當前最小生成樹邊權和。

樣例一

input

2 2
Add 1 2
Return

output

1
0

樣例二

input

5 10
Add 2 1
Add 3 2
Add 4 2
Add 5 2
Add 2 3
Return
Delete 1
Add 2 3
Add 5 2
Return

output

0
0
0
10
10
10
0
0
15
0

樣例三

見樣例資料下載

限制與約定

測試點編號 n m 其他
\(1、2、3\) \(n≤10^3\) \(m≤10^3\) 只有\(Add\)操作
\(4\) \(n≤2×10^5\) \(m≤2×10^5\) 只有\(Add\)操作
\(5\) \(n≤3×10^5\) \(m≤5×10^5\)
\(6\) \(n≤2×10^5\) \(m≤2×10^5\) 沒有\(Return\)操作
\(7\) \(n≤3×10^5\) \(m≤5×10^5\)
\(8\) \(n≤2×10^5\) \(m≤2×10^5\)
\(9、10\)
\(n≤3×10^5\) \(m≤5×10^5\)

時間限制:\(1s\)

空間限制:\(64MB\)

分析

我們認真分析題目的話,不難得出以下結論

\(1\)、此題的空間限制較小,如果用可持久化資料 結構很可能會超空間

\(2\)、邊權是從小到大加入的,因此如果之前加的邊已經可以生成一棵樹,那麼之後加的邊不會產生影響

\(3\)、題目中沒有要求強制線上,而且有\(Return\)操作,可以考慮離線

對於\(Add\)操作,我們可以像最小生成樹用並查集維護

如果當前的兩個節點已經處於一個聯通塊,那麼它們之間的權值一定小於新加入的權值,我們不去管他

如果兩個節點屬於一個聯通塊,我們就把兩個節點並在一起

對於\(Delete\)操作,路徑壓縮並查集是無法解決的,因為它會打亂原來的結果,所以我們要用到按秩合併的並查集

所謂按秩合併,就是把深度小的併到深度大的上面或者是把子樹小的併到子樹大的上面

對於\(Return\)操作,因為是離線處理,所以就好辦多了

如果撤銷加邊操作的話,我們把撤銷操作改為刪邊操作即可

如果撤銷刪除操作,我們就不去刪邊,而是用一個\(ans\)陣列去記錄有\(k\)條邊時的最小生成樹的權值

程式碼

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;//不開long long見祖宗
inline ll read(){
	register ll x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1LL)+(x<<3LL)+(ch^48);
		ch=getchar();
	}
	return x*f;
}//快讀卡常
struct asd{
	char jls[3];
	ll jla,jlb;
}b[maxn];//離線用結構體
ll fa[maxn],rk[maxn],shuyu[maxn],sta[maxn],top;
ll ans[maxn],n,m,tot,cnt,anscnt;
//fa:並查集父親節點,rk:子樹大小,shuyu:記錄某條邊屬於哪一個節點,sta:儲存使用過的邊
//ans:記錄邊數為k時最小生成樹的權值和,tot:記錄最小生成樹的權值,cnt:記錄最小生成樹的邊數,anscnt:記錄統計過的答案的個數
ll zhao(ll xx){
	if(xx==fa[xx]) return xx;
	return zhao(fa[xx]);
}//找到祖先節點
void bing(ll xx,ll yy,ll w){
	ll aa=zhao(xx),bb=zhao(yy);
	if(aa==bb){
		shuyu[w]=-1;
		return;
	}
	if(rk[aa]<rk[bb]) swap(aa,bb);
	rk[aa]+=rk[bb];
	fa[bb]=aa;
	shuyu[w]=bb;
	tot+=w,cnt++;
}//按秩合併
void shanchu(ll val){
	ll xx=shuyu[val];
	ll yy=fa[xx];
	rk[yy]-=rk[xx];
	fa[xx]=xx;
	cnt--,tot-=val;
}//刪邊
int main(){
	n=read(),m=read();
	for(ll i=1;i<=n;i++){
		fa[i]=i,rk[i]=1;
	}
	for(ll i=1;i<=m;i++){
		scanf("%s",b[i].jls);
		if(b[i].jls[0]=='A'){
			b[i].jla=read(),b[i].jlb=read();
		} else if(b[i].jls[0]=='D'){
			b[i].jla=read();
		}
	}
	//初始化+讀入
	for(ll i=1;i<=m;i++){
		if(b[i].jls[0]=='A'){
			bing(b[i].jla,b[i].jlb,i);
			sta[++top]=i;
			if(cnt<n-1) ans[++anscnt]=0;
			else ans[++anscnt]=tot;
			if(b[i+1].jls[0]=='R'){
				b[i+1].jls[0]='D';
				b[i+1].jla=1;
			}
			printf("%lld\n",ans[anscnt]);
		} else if(b[i].jls[0]=='D'){
			if(b[i+1].jls[0]=='R'){
				printf("%lld\n%lld\n",ans[anscnt-b[i].jla],ans[anscnt]);
				continue;
			}
			while(b[i].jla--){
				ll now=sta[top--];
				anscnt--;
				if(shuyu[now]!=-1){
					shanchu(now);
				}
			}
			if(cnt<n-1) ans[anscnt]=0;
			else ans[anscnt]=tot;
			printf("%lld\n",ans[anscnt]);
		}
	}
	return 0;
}