luogu P9261 [PA 2022] Płótno / QOJ5242 Płótno [B] 题解
考虑如何快速计算联通块数量。对于一个森林,联通块数量就为点数减去边数,但是有环就会少算,我们还需要加上 \(2\times 2\) 方格的数量。
考虑离线扫描线,右端点从左往右遍历,我们用线段树维护前 \(k+1\) 大值和个数。新加入一个点时,先把所有包含这个点的区间加 \(1\),再把包含它与直接相邻的点连成的边区间加 \(1\),然后把包含它新增的 \(2\times 2\) 方格的区间减 \(1\)。
需要注意的是,因为它是一个环,所以如果出现一个联通块同时达到左边界和右边界,联通块数量会被判为 \(0\),所以我们把 \(0\) 的答案加到 \(1\) 里面。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lc(i) ((i)<<1)
#define rc(i) ((i)<<1|1)
const int N=5e5+5,M=15;
const int inf=1e9;
int n,k,a[2][N];ll ans[M];pair<int,int> pos[N];
pair<int,ll> mn[N<<2][M];ll add[N<<2];
void pushup(int i){
for(int j=1,x=1,y=1;j<=k+1;j++){
if(mn[lc(i)][x].first<mn[rc(i)][y].first){
mn[i][j]=mn[lc(i)][x];
x++;
}else if(mn[lc(i)][x].first==mn[rc(i)][y].first){
mn[i][j]={mn[lc(i)][x].first,mn[lc(i)][x].second+mn[rc(i)][y].second};
x++;y++;
}else{
mn[i][j]=mn[rc(i)][y];
y++;
}
}
}
void apply_add(int i,ll v){
for(int j=1;j<=k+1;j++)mn[i][j].first+=v;
add[i]+=v;
}
void pushdown(int i){
if(add[i]){
apply_add(lc(i),add[i]);
apply_add(rc(i),add[i]);
add[i]=0;
}
}
void build(int i,int tl,int tr){
if(tl==tr){
mn[i][1]={0,1};
for(int j=2;j<=k+1;j++)mn[i][j]={inf,0};
return;
}
int tm=(tl+tr)>>1;
build(lc(i),tl,tm);
build(rc(i),tm+1,tr);
pushup(i);
}
void update(int i,int tl,int tr,int l,int r,ll v){
if(l<=tl&&tr<=r){
apply_add(i,v);
return;
}
pushdown(i);
int tm=(tl+tr)>>1;
if(l<=tm)update(lc(i),tl,tm,l,r,v);
if(r>tm)update(rc(i),tm+1,tr,l,r,v);
pushup(i);
}
int main(){
// freopen("canvas.in","r",stdin);
// freopen("canvas.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=0;i<=1;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
pos[a[i][j]]={i,j};
}
}
build(1,1,2*n);
for(int i=1;i<=2*n;i++){
int x=pos[i].first,y=pos[i].second;
int l=a[x][y-1+(y==1?n:0)],r=a[x][y+1-(y==n?n:0)],L=a[x^1][y-1+(y==1?n:0)],R=a[x^1][y+1-(y==n?n:0)],u=a[x^1][y];
update(1,1,2*n,1,i,1);
if(max(l,max(L,u))<i)update(1,1,2*n,1,min(l,min(L,u)),1);
if(max(r,max(R,u))<i)update(1,1,2*n,1,min(r,min(R,u)),1);
if(l<i)update(1,1,2*n,1,l,-1);
if(r<i)update(1,1,2*n,1,r,-1);
if(u<i)update(1,1,2*n,1,u,-1);
for(int j=1;j<=k+1;j++){
if(mn[1][j].first>k)break;
if(mn[1][j].first==0)ans[1]+=mn[1][j].second-2*n+i;//后面的左端点还没有算到,这些区间不存在,所以需要减去
ans[mn[1][j].first]+=mn[1][j].second;
}
}
for(int i=1;i<=k;i++)cout<<ans[i]<<" ";
return 0;
}

浙公网安备 33010602011771号