CF1140F解题报告
这是一道我自己认为线段树分治场切的一道好题。适合以后来复习。
题目大意:
一个二维集合 \(S\),令 \(R=S\),如果 \((x_1,y_1)\in R,(x_1,y_2)\in R,(x_2,y_1)\in R\) 那么 \((x_2,y_2)\) 也一定在 \(R\) 里面。动态修改 \(S\),求修改后的 \(|R|\)。
这道题,我们可以先分析 \((x_1,y_1)\in R,(x_1,y_2)\in R,(x_2,y_1)\in R\) 有什么性质。
因为第二维与第一维的元素是独立的,所以我们试着把它分为两行。
一个二元组就是上面一行向下一行连边。
考虑一个上面和下面连接的元素数量,如果上面有两个不一样的元素都向下面同一个元素连了边,就是满足了 \((x_1,y_1)\in R,(x_2,y_1)\in R\)。只需要找到上面连边的其他元素就能连边。设一个连通块上面的数量为 \(a\) 个,下面有 \(b\) 个,则总体的边数应该是 \(ab\) 条边。
我们设计一个并查集,记录一下 \(sza\)、\(szb\)、\(sz\)、\(fath\) 就行。每一次连边就是把所有 \(sz\) 相加。使用可撤销并查集。
考虑怎么记录答案。直接将原来的两个连通块的答案删除,再加上新的答案即可。
对于后面的撤销操作,直接将答案还原成为还没有更改时的答案即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
namespace gtx{
// Fast IO
void read(int &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void read(char &x){do{x=getchar();}while(x==' '||x=='\n'||x=='\r');}
void write(char x){putchar(x);}
void write(int x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do st[++tot]=x%10,x/=10; while(x);
while(tot){putchar(st[tot--]+'0');}
}
void write(int x,char y){write(x);write(y);}
#ifndef int
void read(long long &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do st[++tot]=x%10,x/=10; while(x);
while(tot){putchar(st[tot--]+'0');}
}
void write(long long x,char y){write(x);write(y);}
#endif
const int MAXN = 6e5+10;
struct segmentree{
int l,r;
vector<pair<int,int>> v;
}tree[MAXN<<2];
void build(int k,int l,int r){
tree[k].l = l;
tree[k].r = r;
if(l==r) return;
int mid = (l+r)>>1;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
}
void modify(int k,int l,int r,int x,int y){
if(tree[k].l>r||tree[k].r<l) return;
if(tree[k].l>=l&&tree[k].r<=r){
tree[k].v.push_back({x,y});
return;
}
modify(k*2,l,r,x,y);
modify(k*2+1,l,r,x,y);
}
int fath[MAXN];
int sza[MAXN],szb[MAXN],sz[MAXN];
int get_father(int k){
if(k==fath[k]) return k;
return get_father(fath[k]);
}
struct ids{
int id;
int sza;
int szb;
int sz;
};
stack<pair<pair<int,ids>,pair<int,ids>>> st;
int nowans;
void merge(int x,int y){
int fx = get_father(x);
int fy = get_father(y);
if(fx==fy) return st.push({{fx,{fx,sza[fx],szb[fx],sz[fx]}},
{fy,{fy,sza[fy],szb[fy],sz[fy]}}}),void();
if(sz[fx]>sz[fy]) swap(fx,fy);
nowans -= sza[fx]*szb[fx];
nowans -= sza[fy]*szb[fy];
st.push({{fx,{fx,sza[fx],szb[fx],sz[fx]}},
{fy,{fy,sza[fy],szb[fy],sz[fy]}}});
fath[fx] = fy;
sz[fy] += sz[fx];
sza[fy] += sza[fx];
szb[fy] += szb[fx];
nowans += sza[fy]*szb[fy];
}
void CX(){
auto tmp = st.top();st.pop();
fath[tmp.first.first] = tmp.first.first;
sz[tmp.first.first] = tmp.first.second.sz;
sza[tmp.first.first] = tmp.first.second.sza;
szb[tmp.first.first] = tmp.first.second.szb;
fath[tmp.second.first] = tmp.second.first;
sz[tmp.second.first] = tmp.second.second.sz;
sza[tmp.second.first] = tmp.second.second.sza;
szb[tmp.second.first] = tmp.second.second.szb;
}
int ans[MAXN];
void solve(int k){
int tot = 0;
int rightans = nowans;
for(auto i:tree[k].v){
merge(i.first,i.second+3e5);
tot++;
}
if(tree[k].l==tree[k].r){
ans[tree[k].l] = nowans;
nowans = rightans;
while(tot--) CX();
return;
}
solve(k*2);
solve(k*2+1);
nowans = rightans;
while(tot--) CX();
}
int n;
map<pair<int,int>,int> mp;
signed main(){
read(n);
fill(sza+1,sza+1+300000,1);
fill(szb+1+300000,szb+600000+1,1);
fill(sz+1,sz+1+600000,1);
iota(fath+1,fath+1+600000,1);
build(1,1,n+1);
for(int i = 1;i<=n;i++){
int x,y;
read(x);read(y);
if(mp.find({x,y})==mp.end()){
mp[{x,y}] = i;
}else{
modify(1,mp[{x,y}],i-1,x,y);
mp.erase({x,y});
}
}
for(auto i:mp){
modify(1,i.second,n+1,i.first.first,i.first.second);
}
solve(1);
for(int i = 1;i<=n;i++) write(ans[i],' ');
return 0;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// gtx::read(T);
while(T--) gtx::main();
return 0;
}