本题有个特殊的性质,就是加边的时候可以保证边是从小到大加的,因为操作编号是从小到大的。
那么先考虑加边。如果这朵边的两个端点已经在同一个集合中,就可以不管这条边,但是还是要把这条边存到栈里!!因为到时候删边的时候要用。
如果这坨边连接了两个不连通的集合,就用启发式合并把这两个集合合并。这样可以保证复杂度是对的。
如果是路径压缩的话可以存在操作使其复杂度为O(n),然后一直撤销鬼畜就炸了。
然后如果Delete操作在Return操作前面可以事先存下结果直接输出。
Delete一个节点p的时候把p的所有祖先的siz减掉,把p的父亲置为它自己就行了。
用ans[x]存储当前有x条边的答案。
use_edge[d]=-1表示不需要d这条边,因为d两端早已连通,而后来的边权一定要大一些,一定不会用到最小生成树上。
如果需要这条边,use_edge[d]则表示合并的时候siz小一些的集合的那个根。后面删边要用。
注意,tot表示实际有多少条边,cnt_edge表示最小生成树(可能还未连通)上有多少条边,删边的时候注意减的顺序。
然后删的边用一个栈来维护就好。要开longlong。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;
int n,m,a,b,k,fa[maxn],use_edge[maxn];
char op[maxn][10];int x[maxn],y[maxn],siz[maxn];
ll ans[maxn],cur;
stack<int> Q;
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
inline void print(ll x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
inline int getfa(int x){return (fa[x]==x)?(x):(getfa(fa[fa[fa[x]]]));}
inline void Delete(int p){for(int u=fa[p];fa[u]!=u;u=fa[u])siz[u]-=siz[p];fa[p]=p;}
inline void addedge(int x,int y,int d){
int px=getfa(x),py=getfa(y);Q.push(d);
if(px==py){use_edge[d]=-1;return;}
if(siz[px]>siz[py]) swap(px,py);
fa[px]=py,siz[py]+=siz[px],use_edge[d]=px;
}
inline void solve(){
int tot=0,cnt_edge=0;
for(int i=1;i<=n;++i) fa[i]=i,siz[i]=1;
for(int i=1;i<=m;++i){
if(op[i][0]=='A'){
addedge(x[i],y[i],i);
if(use_edge[i]!=-1) cur+=i,cnt_edge++;
ans[++tot]=(cnt_edge<n-1)?0:cur;
print(ans[tot]),putchar('\n');
if(op[i+1][0]=='R') op[i+1][0]='D',x[i+1]=1;
}
if(op[i][0]=='D'){
if(op[i+1][0]=='R'){
print(ans[tot-x[i]]),putchar('\n');
print(ans[tot]),putchar('\n');
continue;
}
while(x[i]--){
int p=Q.top();Q.pop(),tot--;
if(use_edge[p]==-1) continue;
Delete(use_edge[p]),cur-=p,cnt_edge--;
}
ans[tot]=(cnt_edge<n-1)?0:cur;
print(ans[tot]),putchar('\n');
}
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;++i){
scanf("%s",op[i]);
if(op[i][0]=='A') x[i]=read(),y[i]=read();
if(op[i][0]=='D') x[i]=read();
}solve();
return 0;
}