P4219 [BJOI2014] 大融合 题解
对于第一种操作,可以用并查集去实现,对于第二种操作应该怎么办?第二种操作其实等价于去求一条边两侧的联通块点的数量的乘积,因为这条边两侧的点都可以通过这条边对查询产生贡献。
但是现在我们查询这个边的时候并没有办法去实现,为什么?因为当执行到这个查询的时候,它就是一个联通块,我们现在没有办法去统计这个边两边的点的数量。对于这个问题,我们可以利用可撤销并查集结合线段树分治去解决。一条边如果被查询,那么就让它在查询的时刻之前和查询的时刻之后被连上,恰巧在这个时候断开就可以。这样的话,每次当我们在线段树递归到子节点的时候,如果是查询操作,一定是恰好让当前应该插入的边都被插入,并且查询的这条边没有被插入。
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n,q;
const int N=1e5+10;
int p[N];
ll siz[N],ans[N];
int a[N],b[N];
bool is[N];
#define mid ((l+r)>>1)
#define ls (u<<1)
#define rs ((u<<1)|1)
struct edge{
int u,v;
bool operator<(const edge&other)const{
if(u==other.u) return v<other.v;
return u<other.u;
}
};
stack<edge> stk;
vector<edge> tr[N<<2];
map<edge,int> mp;
int find(int i){
return p[i]==i?i:find(p[i]);
}
void unite(int x,int y){
int fx=find(x),fy=find(y);
if(fx^fy){
if(siz[fx]<siz[fy]) swap(fx,fy);
siz[fx]+=siz[fy];
p[fy]=fx;
stk.push({fx,fy});
}
}
void add(int u,int l,int r,int jl,int jr,const edge& i){
if(jl>jr||jr<l||jl>r) return ;
if(jl<=l&&r<=jr) return tr[u].push_back(i);
add(ls,l,mid,jl,jr,i);
add(rs,mid+1,r,jl,jr,i);
}
void solve(int u,int l,int r){
int now=stk.size();
for(auto [x,y]:tr[u]){
unite(x,y);
}
if(l==r){
if(is[l]){
ans[l]=siz[find(a[l])]*siz[find(b[l])];
}
}else{
solve(ls,l,mid);
solve(rs,mid+1,r);
}
while(stk.size()>now){
auto [x,y]=stk.top();stk.pop();
siz[x]-=siz[y];
p[y]=y;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++) siz[i]=1,p[i]=i;
for(int i=1;i<=q;i++){
char c;
int u,v;
cin>>c>>u>>v;
a[i]=min(u,v),b[i]=max(u,v);
if(c=='A'){
mp.insert({edge{a[i],b[i]},i});
}else{
is[i]=true;
auto it=mp.find(edge{a[i],b[i]});
add(1,1,q,it->second,i-1,edge{a[i],b[i]});
it->second=i+1;
}
}
for(auto [key,val]:mp){
if(val<=q) add(1,1,q,val,q,edge{key.u,key.v});
}
solve(1,1,q);
for(int i=1;i<=q;i++){
if(ans[i]){
cout<<ans[i]<<'\n';
}
}
return 0;
}

浙公网安备 33010602011771号