题解 P4898【[IOI2018] seats 排座位】

$\text{Link}$

前言

这道题并不是我自己思考出来的,而是借助了其余题解的帮助。

写这个题解主要为了加深理解,想不看题解自己写一遍出来,确认自己已经懂了这道题。您可以将这篇题解看成我的做题记录。

尽可能地写得清晰一些,哪部分看不懂可以留言提醒。

$\text{Update 2022.6.29}$:修正一处笔误。

题意

有一个 $n\times m$ 的点构成的 $n\times m$ 的矩阵,设 $S(i)$ 表示编号为 $1$ 至 $i$ 的点是否构成矩形,$q$ 次操作,每次交换两个编号的点的位置,然后求出 $|\{i|S(i)\}|$。

$1\le n\times m\le 10^6,1\leq q\le5\times 10^4$.

思路

$\text{Section 1}$ 转化

首先我们思考怎么对于一个 $i$ 求出 $S(i)$。

与其他题解一样,我们将前 $i$ 个点染为黑色,其余染成白色。

我们考虑使用染出来的颜色设计出 $S(i)$ 为真的充要条件。

成为矩形的充要条件有:

  • 点处于一个连通块内
  • 图形不存在“$\text{L}$ 型”。

对于条件 $1$,我们有:

  • 对于所有黑点,其左上方两点中均不为黑色的点数为 $1$。

对于条件 $2$,我们有:

  • 对于所有白点,与其四连通的点内有不少于两个黑点的个数为 $0$。

于是我们可以依次计算。

$\text{Section 2}$ 优化

我们设点 $(i,j)$ 的编号为 $num_{(i,j)}$,编号为 $i$ 的点为 $(x_i,y_i)$。

考虑对于 $\text{Section 1}$ 的算法将 $i$ 由 $1$ 枚举到 $n\times m$,则 $(i,j)$ 被染为黑色的时间点为 $num_{(i,j)}$。

我们考虑对于点 $(a,b)$,其满足条件 $1$、$2$ 的时间段。

设 $i$ 为当前时间点。

此处我们不考虑边缘情况。

若满足条件 $1$,则:

  • $(a,b)$ 是黑点,即 $num_{(a,b)}\le i$;
  • $(a,b)$ 左方是白点,即 $num_{(a-1,b)}>i$ ;
  • $(a,b)$ 上方是白点,即 $num_{(a,b-1)}>i$。

对这些条件取并集,即 $i\in[num_{(a,b)},\min(num_{(a-1,b)},num_{(a,b-1)}))$。

若满足条件 $2$,则:

  • $(a,b)$ 是白点,即 $num_{(a,b)}>i$;
  • $(a,b)$ 四周 $num$ 次小的值所在的点为黑点,即 $\text{secmin}(num_{(a-1,b)},num_{(a,b-1)},num_{(a+1,b)},num_{(a,b+1)})\le i$。

对这些条件取并集,即 $i\in[\text{secmin}(num_{(a-1,b)},num_{(a,b-1)},num_{(a+1,b)},num_{(a,b+1)}),num_{(a,b)})$。

此处需要搞清楚点被染黑点的条件、点被染成黑色的时间段等概念,不能混淆。

$\text{Section 3}$ 数据结构

我们需要一种数据结构,支持上面的操作。

对于每个点我们维护 $[$ 满足条件 $1\,]+[$ 满足条件 $2\,]$,那么我们对于每个合法区间进行 $+1$,查询全局有几个 $1$。

我们可以将满足条件一的点数设为 $x$,满足条件二的点数设为 $y$。

显然 $x\ge 1,y\ge0$,即 $x+y\ge 1$。

所以维护区间最小值及出现次数即答案.

$\text{Section 4}$ 实现细节

  • 1.线段树初始最小值为 $0$ 情况的处理:

    我这里也采用了维护严格次小值及其出现次数的方法,查询判断最小值是否为 $1$,严格次小值是否为 $1$ 即可。

  • 2.修改点集出现重叠情况时需要去重。

  • 3.我们将二维的 $num$ 数组重编号为 $1$ 至 $n\times m$ 维护较为方便。

  • 4.考虑边界问题,设计的重编号函数检测到越界时可以返回 $n\times m+1$,此时 $num_{n\times m+1}=n\times m+1$,线段树区间修改时判断当修改区间左端点大于右端点是返回即可。


最终时间复杂度为 $O((nm+q)\log nm)$,常数巨大就对了,带个线段树的 $4$ 和一次修改的 $2$、修改点数的 $10$。

代码(挺容易写):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define mpr make_pair
namespace IO{//by cyffff

}
const int N=1e6+10,INF=1e7;
int num[N];
pii pos[N];
int n,m,su;
int mx[]={0,1,0,-1,0};
int my[]={1,0,-1,0,0};
inline int number(int x,int y){
    if(min(x,y)<1||x>n||y>m) return su;
    return (x-1)*m+y;
}
struct Segument_Tree{
    struct node{
        int l,r,minn,tag,cnt,secm=INF,sec;
        inline node operator+=(const int &x){
            minn+=x;
            secm+=x;
            tag+=x;
            return *this;
        }
    }a[N<<2];
    #define ls (rt<<1)
    #define rs (rt<<1|1)
    inline void getsec(int &x,int &y,int &sx,int &sy,int a,int b){
        if(x>a){
            sx=x,sy=y,x=a,y=b;
        }else if(x==a){
            y+=b;
        }else if(sx>a){
            sx=a,sy=b;
        }else if(sx==a){
            sy+=b;
        }
    }
    inline void pushup(int rt){
        a[rt].cnt=a[rt].sec=0;
        a[rt].minn=a[rt].secm=INF;
        getsec(a[rt].minn,a[rt].cnt,a[rt].secm,a[rt].sec,a[ls].minn,a[ls].cnt);
        getsec(a[rt].minn,a[rt].cnt,a[rt].secm,a[rt].sec,a[ls].secm,a[ls].sec);
        getsec(a[rt].minn,a[rt].cnt,a[rt].secm,a[rt].sec,a[rs].minn,a[rs].cnt);
        getsec(a[rt].minn,a[rt].cnt,a[rt].secm,a[rt].sec,a[rs].secm,a[rs].sec);
    }
    inline void pushdown(int rt){
        if(!a[rt].tag) return ;
        a[ls]+=a[rt].tag;
        a[rs]+=a[rt].tag;
        a[rt].tag=0;
    }
    inline void build(int rt,int l,int r){
        a[rt].l=l,a[rt].r=r;
        a[rt].cnt=r-l+1;
        if(l==r) return ;
        int mid=l+r>>1;
        build(ls,l,mid);
        build(rs,mid+1,r);
    }
    inline void update(int rt,int l,int r,int v){
        if(l>r) return ;
        if(a[rt].l>=l&&r>=a[rt].r){
            a[rt]+=v;
            return ;
        }
        pushdown(rt);
        if(a[ls].r>=l) update(ls,l,r,v);
        if(a[rs].l<=r) update(rs,l,r,v);
        pushup(rt);
    }
    inline int query(){
        return (a[1].minn==1)*a[1].cnt+(a[1].secm==1)*a[1].sec;
    }
}t;
int tind[10];
inline void update(int x,int y,int v){
    int posi=number(x,y);
    if(posi==su) return ;
    t.update(1,num[posi],min(num[number(x-1,y)],num[number(x,y-1)])-1,v);
    for(int i=0;i<4;i++){
        tind[i]=num[number(x+mx[i],y+my[i])];
    }
    sort(tind,tind+4);
    t.update(1,tind[1],num[posi]-1,v);
}
pii tmp[15],tmp1,tmp2;
int q,top;
int main(){
    n=read(),m=read(),q=read();
    num[su=n*m+1]=n*m+1;
    for(int i=1;i<su;i++){
        int x=read()+1,y=read()+1;
        num[number(x,y)]=i;
        pos[i]=mpr(x,y);
    }
    t.build(1,1,su-1);
    for(int i=1;i<su;i++){
        update(pos[i].first,pos[i].second,1);
    }
    while(q--){
        int x=read()+1,y=read()+1;
        tmp1=pos[x],tmp2=pos[y];
        top=0;
        for(int i=0;i<5;i++){
            tmp[++top]=mpr(tmp1.first+mx[i],tmp1.second+my[i]);
            tmp[++top]=mpr(tmp2.first+mx[i],tmp2.second+my[i]);
        }
        sort(tmp+1,tmp+top+1);
        top=unique(tmp+1,tmp+top+1)-tmp;
        for(int i=1;i<top;i++){
            update(tmp[i].first,tmp[i].second,-1);
        }
        swap(num[number(tmp1.first,tmp1.second)],num[number(tmp2.first,tmp2.second)]);
        swap(pos[x],pos[y]);
        for(int i=1;i<top;i++){
            update(tmp[i].first,tmp[i].second,1);
        }
        write(t.query());
        putc('\n');
    }
    flush();
    return 0;
}

再见 qwq~

posted @ 2021-06-09 23:03  ffffyc  阅读(45)  评论(0)    收藏  举报  来源