题解 [IOI2018]Seats 排座位
题目链接
题意描述
你有一个\(n\times m\)的座位表,每一格都有一位\(OIer\),编号给定,定义座位表的美妙度为所有编号为\(1\sim i\)的\(OIer\)座位集合且集合中的元素恰好构成一个矩形的\(i\)的个数,\(q\)次询问,每次询问交换两个\(OIer\)的座位,输出交换后座位表的美妙度。
\(n\times m\le 10^6,q\le5\times 10^4\)
\(\large{Solution:}\)
这个问题很棘手,就算能迅速判定一个\(i\)时间复杂度也达到\(O(nmq)\),肯定是要以\(1\sim n\times m\)区间考虑的。
观察一个矩形有什么性质:
\(Observation\ \mbox{I}:\)矩形是连通的(这不废话),因此矩形内仅有四角与矩形外的格子有两格是四连通的,它们是等价的,因此只需要保证左上角满足性质。
\(Observation\ \mbox{II}:\)矩形不存在拐角(这不也是废话),因此矩形外的格子最多有与矩形有一格四连通。
容易证明,以上两条性质\(\Leftrightarrow\)集合元素构成矩形。
于是我们从两句废话中得到了矩形的关键性质。
我们由此考虑维护两类点(矩形左上角的点和矩形外使矩形出现拐角的点),设为黑点和白点,如果按照编号由\(1\)枚举到\(n\times m\),可以用几个简单的信息表示出两类点的影响时间范围:
\(\small{Points\ in\ Black:}\)
\(\text{I.}tim\ge id_{x,y}\)(显然必须在集合中)
\(\text{II.}tim<\min(id_{x-1,y},id_{x,y-1}))\)(左边和上方的格子不在集合中)
\(\Rightarrow tim\in [id_{x,y},\min(id_{x-1,y},id_{x,y-1}))\)
\(\small{Points\ in\ White:}\)
\(\text{I.}tim<id_{x,y}\)(显然必须在集合外)
\(\text{II.}tim\ge Secondmin(id_{x-1,y},id_{x,y-1},id_{x+1,y},id_{x,y+1})\)(可能有点绕,其实就是第二小的格子进入集合了那就一定至少与集合内两格连通了)
\(\Rightarrow tim\in [Secondmin(id_{x-1,y},id_{x,y-1},id_{x+1,y},id_{x,y+1}),id_{x,y})\)
有了这两个信息,那么问题就很简单了,只要两类点在影响时间范围内\(+1\),求有多少个时间点的值恰好为\(1\)即可,容易想到线段树区间修改\(and\)全局查询。修改最多影响\(10\)个点,暴力删除然后加回来就行了。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define inf 1e18
#define N 1000005
#define ls k<<1
#define rs k<<1|1
#define mid ((l+r)>>1)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pii pair<int,int>
#define il inline
#define px OIer[i].x
#define py OIer[i].y
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;
il int read(){
int w=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-h;ch=getchar();}
while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
return w*h;
}
const int dx[]={1,-1,0,0},dy[]={0,0,1,-1};
struct Seat{int x,y,id;pii Sco[2];}OIer[N];
int n,m,q,op[4];bool rev;
vector<int>id[1005];
namespace SGT{
struct Data{int Min,Sum,Tag;}tr[N<<2];
void Pushup(int k){
tr[k].Min=min(tr[ls].Min,tr[rs].Min);
tr[k].Sum=tr[ls].Sum*(tr[ls].Min==tr[k].Min)+tr[rs].Sum*(tr[rs].Min==tr[k].Min);
}
void Update(int k,int v){tr[k].Min+=v;tr[k].Tag+=v;}
void Pushdown(int k){if(tr[k].Tag)Update(ls,tr[k].Tag),Update(rs,tr[k].Tag),tr[k].Tag=0;}
void Build(int k,int l,int r){
tr[k].Sum=r-l+1;
if(l==r)return;
Build(ls,l,mid);
Build(rs,mid+1,r);
}
void Modify(int k,int l,int r,int x,int y,int z){
if(l>y||r<x||x>y)return;
if(l>=x&&r<=y)return Update(k,z);
Pushdown(k);
if(x<=mid)Modify(ls,l,mid,x,y,z);
if(mid<y)Modify(rs,mid+1,r,x,y,z);
Pushup(k);
}
int Query(){return tr[1].Sum;}
}
using namespace SGT;
void Insert(int i,int j){
OIer[i].Sco[0]=mp(i,min(id[px-1][py],id[px][py-1])-1);
Modify(1,1,n*m,OIer[i].Sco[0].fi,OIer[i].Sco[0].se,j);
op[0]=id[px-1][py];op[1]=id[px][py-1];op[2]=id[px+1][py];op[3]=id[px][py+1];
sort(op,op+4);OIer[i].Sco[1]=mp(op[1],i-1);
Modify(1,1,n*m,OIer[i].Sco[1].fi,OIer[i].Sco[1].se,j);
}
signed main(){
n=read();m=read();q=read();
if(n>m)swap(n,m),rev=1;
for(int i=1;i<=n*m;i++){
int x=read()+1,y=read()+1;
if(rev)swap(x,y);
OIer[i]=(Seat){x,y,i};
}
Build(1,1,n*m);
sort(OIer+1,OIer+n*m+1,[](Seat x,Seat y){return(x.x==y.x)?x.y<y.y:x.x<y.x;});
for(int i=0;i<=m+1;i++)id[0].pb(inf),id[n+1].pb(inf);
for(int i=1;i<=n;i++)id[i].pb(inf);
for(int i=1;i<=n*m;i++)id[(i-1)/m+1].pb(OIer[i].id);
for(int i=1;i<=n;i++)id[i].pb(inf);
sort(OIer+1,OIer+n*m+1,[](Seat x,Seat y){return x.id<y.id;});
for(int i=1;i<=n*m;i++)Insert(i,1);
int pos[10];
while(q--){
int A=read()+1,B=read()+1,cnt=0,nx,ny;
pos[cnt++]=A;pos[cnt++]=B;
for(int i=0;i<4;i++){
nx=OIer[A].x+dx[i];ny=OIer[A].y+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m)pos[cnt++]=id[nx][ny];
nx=OIer[B].x+dx[i];ny=OIer[B].y+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m)pos[cnt++]=id[nx][ny];
}
sort(pos,pos+cnt);
cnt=unique(pos,pos+cnt)-pos;
for(int i=0;i<cnt;i++)Insert(pos[i],-1);
swap(OIer[A],OIer[B]);
swap(id[OIer[A].x][OIer[A].y],id[OIer[B].x][OIer[B].y]);
for(int i=0;i<cnt;i++)Insert(pos[i],1);
printf("%lld\n",Query());
}
return 0;
}