[loj2864] [ioi2018 d1t2] seats - 线段树

传送门:https://loj.ac/problem/2864

作死尝试这道全世界只有1人ac的神题。

先%一波dsgsjk,这位吊打全世界的julao可能是全世界第二个做出这题的现役oi选手。

题目大意:给定一个n*m的矩阵,矩阵中不重不漏地分布着0~n*m-1的元素。称一个大小为s的连续子矩阵是好的当且仅当其中不重不漏地出现了0~s-1的元素。每次交换矩阵中的两个元素,查询当前有多少个好的子矩阵。强制在线。

这题容易给人一种奇技淫巧的数据结构的感觉。那么到底该用什么数据结构,维护什么信息?

我们发现我们可以通过维护前i个元素在矩阵中位置分布相关的信息,来检验前i个元素是否能组成合法子矩阵。

开一棵线段树,其下标为i的位置维护的是前i个元素的信息。

那么问题来了,我们需要维护什么信息,能够支持修改和快速计算答案?

这确实是一个棘手的问题。估计现场应该很多人都跪在这一步了。

我一开始的想法是,维护前i个元素所组成的图形的周长。后来发现据此并不能直接计算答案(一个大小一定的矩形其周长也可能有多种可能),遂放弃。

后来我想到一种“看似可行”的思路:维护前i个元素的x、y坐标的最小值和最大值,这样前i个元素组成合法子矩形当且仅当(xmax - xmin + 1) * (ymax - ymin + 1) = s。

这样我们将每个位置的值设为(xmax - xmin + 1) * (ymax - ymin + 1) - i,这个数一定非负,当且仅当它为0时答案合法。

然而这个想法在修改时遇到了麻烦:交换两个元素,可能导致一个区间的max和min值发生大规模变化,尤其是当把原来的最值移动到后面时,可能造成区间内最值的多次改变,我们可能面临每次操作O(n)次修改。

目前我还没有想到合适的方法来维护这个东西,如果有大神会维护的话麻烦在评论中留言谢谢。

那怎么办?就要祭出dsgsjk大神的天才级思路了:通过维护“特殊点”的信息来实现快速修改和判断合法性的目的!

为了方便,我们现在对于一个位置i,将前i个点染成黑色,后面的点为白色。

我们维护这么两个东西:

1、所有的黑点中,有多少个点的左侧和上侧都不挨着黑点(可能是白点或边缘)。

2、所有的白点中,有多少个点的周围四连通地挨着超过2个黑点。

这两个信息能说明什么?

首先,容易证明所有的合法子矩阵都满足:第一个信息为1,第二个信息为0。接下来我们证明:一个非法的图形不可能同时满足以上两个条件。

容易看出,对于每个黑点组成的四连通块,我们总能选出其最左上的点(至少一个,也可能一个连通块中有多个合法点),使得它的左边和上边都不是黑点。也就是说,黑色连通块数量<=信息1。

因此,一个有>=2个连通块的图形不可能满足信息1数量为1。

再来考虑一个连通块但不是矩形的图形。容易发现这个图形一定存在如下的“拐角”:

xxoo

xxoo

xxxx

xxxx

x表示黑点,o表示白点,2行3列那个白点就是“拐角”。而在拐角处的白点满足信息2。

也就是说,存在拐角的图形不可能满足信息2数量为0。

那么同时满足1和2的图形,就只有“一个连通块且不存在拐角”的图形了——也就是矩形!

这样我们就证明了条件的充要性。那么怎么维护呢?

我们直接维护两个信息的和(这个和显然是正整数),然后统计有多少个位置答案为1。

交换两个位置时,我们可以把预制相邻的所有位置都拿出来(最多不超过10个,注意去重),然后暴力地对每个位置,先扣去原来对信息的贡献,再加上操作后对信息的贡献。对应在线段树上就是O(1)次区间修改。

至于查询区间有多少个1,我们可以用类似维护矩形面积并的方式,记录区间最小值和最小值的个数。

额……或许这道令全世界oi高手望而却步的难题,被dsgsjk做成了一道九省难度的数据结构?

//以下代码根据ioi提交格式编写

#include<bits/stdc++.h>
using namespace std;
#include"seats.h"
int n,m,p,k,jz[1000010],vl[1000010];
struct wz{
	int x,y;
}a[1000010];
int t[4000010],s[4000010],c[4000010];
#define ls q << 1
#define rs q << 1 | 1
#define ln ls,l,mid
#define rn rs,mid + 1,r
#define md int mid = l + r >> 1
void build(int q,int l,int r){
	if(l == r){
		t[q] = vl[l];s[q] = 1;return;
	}
	md;
	build(ln);build(rn);
	t[q] = min(t[ls],t[rs]);
	s[q] = 0;if(t[q] == t[ls]) s[q] += s[ls];if(t[q] == t[rs]) s[q] += s[rs];
}
inline void ps(int q){
	t[ls] += c[q];c[ls] += c[q];
	t[rs] += c[q];c[rs] += c[q];
	c[q] = 0;
}
void ins(int q,int l,int r,int al,int ar,int x){
	if(al > ar) return;
	if(l >= al && r <= ar){
		t[q] += x;c[q] += x;return;
	}
	ps(q);md;
	if(mid >= al) ins(ln,al,ar,x);
	if(mid < ar) ins(rn,al,ar,x);
	t[q] = min(t[ls],t[rs]);
	s[q] = 0;if(t[q] == t[ls]) s[q] += s[ls];if(t[q] == t[rs]) s[q] += s[rs];
}
int dx[4] = {0,-1,0,1},dy[4] = {-1,0,1,0};
#define mt(x,y) jz[(x - 1) * m + (y)]
#define lx(x) (x + dx[0])
#define ux(x) (x + dx[1])
#define fx(x,i) (x + dx[i])
#define fy(x,i) (x + dy[i])
#define ly(x) (x + dy[0])
#define uy(x) (x + dy[1])
#define chk(x,y) (x >= 1 && x <= n && y >= 1 && y <= m)
#define ax(q) a[q].x
#define ay(q) a[q].y
#define qwq(x,y) mt(fx(ax(x),y),fy(ay(x),y))
inline int ed(int q){
	int mn = p + 1;
	if(chk(lx(ax(q)),ly(ay(q)))) mn = min(mn,qwq(q,0));
	if(chk(ux(ax(q)),uy(ay(q)))) mn = min(mn,qwq(q,1));
	return mn;
}
inline int bg(int q){
	int mn1 = p + 1,mn2 = p + 1;
	for(int i = 0;i < 4;++i) if(chk(fx(ax(q),i),fy(ay(q),i))){
		if(qwq(q,i) < mn1) mn2 = mn1,mn1 = qwq(q,i);
		else if(qwq(q,i) < mn2) mn2 = qwq(q,i);
	} 
	return mn2;
}
int st[15],ft;
void give_initial_chart(int h,int c,vector<int> x,vector<int> y){
	n = h;m = c;p = n * m;
	for(int i = 1;i <= p;++i){
		a[i].x = x[i - 1] + 1;a[i].y = y[i - 1] + 1;mt(ax(i),ay(i)) = i;
	}
	for(int i = 1;i <= p;++i){
		vl[i] = vl[i - 1];
		if(bg(i) < i) --vl[i];
		if(ed(i) > i) ++vl[i];
		for(int j = 0;j < 4;++j) if(chk(fx(ax(i),j),fy(ay(i),j))){
			if(qwq(i,j) < i && ed(qwq(i,j)) == i) --vl[i];
			else if(qwq(i,j) > i && bg(qwq(i,j)) == i) ++vl[i];
		}
	}
	build(1,1,p);
}
int swap_seats(int u,int v){
	++u;++v;if(u > v) swap(u,v);
	if(u == v) return s[1];
	ft = 0;
	st[++ft] = u;st[++ft] = v;
	for(int j = 0;j < 4;++j) if(chk(fx(ax(u),j),fy(ay(u),j))) st[++ft] = qwq(u,j);
	for(int j = 0;j < 4;++j) if(chk(fx(ax(v),j),fy(ay(v),j))) st[++ft] = qwq(v,j);
	sort(st + 1,st + ft + 1);
	for(int j = 1;j <= ft;++j) if(st[j] != st[j - 1]){
		if(bg(st[j]) < st[j]) ins(1,1,p,max(bg(st[j]),u),min(st[j],v) - 1,-1);
		if(ed(st[j]) > st[j]) ins(1,1,p,max(st[j],u),min(ed(st[j]),v) - 1,-1);
	}
	swap(mt(ax(u),ay(u)),mt(ax(v),ay(v)));swap(a[u],a[v]);
	for(int j = 1;j <= ft;++j) if(st[j] != st[j - 1]){
		if(bg(st[j]) < st[j]) ins(1,1,p,max(bg(st[j]),u),min(st[j],v) - 1,1);
		if(ed(st[j]) > st[j]) ins(1,1,p,max(st[j],u),min(ed(st[j]),v) - 1,1);
	}
	return s[1];
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值