[提高组集训2021] 很多OID

一、题目

\(\tt OneInDark\) 这头猪就不要叫了嘛\(...\)

哥哥 \(\tt zxy\) 养了很多头 \(\tt OID\),其中第 \(i\) 行 \(j\) 列的格子是第 \(a_{i,j}\) 头猪。

定义一个子矩形是好的,当且仅当它满足含有猪种类编号最大值减去编号最小值等于矩形大小减一,定义矩形的权值为矩形大小,问所有好的矩形的权值和,答案对 \(998244353\) 取模。

\(n\times m\leq 2\times 10^5\),保证所有 \(\tt OID\) 的编号互不相同。

二、解法

考试时候因为没有看到标黑的条件惨痛暴力分,下次还是要认真读题。

如果 \(n=1\) 就是这道题,用单调栈\(+\)线段树维护最值可以做到 \(O(m\log m)\),但是这种做法难以扩展到二维情况,如果强行枚举较小的那一位,从它开始扫描可以做到 \(O(nm\sqrt {nm}\log nm)\)

换一种思路,我们以前是直接对下标扫描线,现在我们对值域进行扫描

也就是把问题转化成保留 \([l,r]\) 中的点,它是否能构成一个矩形。矩形有神奇判据,我们先把 \([l,r]\) 中的点染黑,那么它构成矩形的充要条件就是:对于每个 \(2\times 2\) 的小矩形(可以越出边界),设 \(f\) 为包含 \(1/3\) 个黑点的小矩形个数,那么 \(f=4\)

解释一下上面这个玄学的东西,首先矩形是闭合的,那么在四个角上会产生一个小矩形,如果边界不只有四个角那么产生的 \(f>4\),如果中间不是满的那么会产生 \(3\) 个黑点的小矩形,这样的图形 \(f>4\)

那么我们扫描 \(r\),维护所有 \(l\) 的 \(f\) 值,因为 \(f\geq 4\) 所以我们维护最小值、最小值的 \(l\) 总和、最小值个数,每次移动 \(r\) 的时候只需要修改那个点对应的四个矩形就行了,时间复杂度 \(O(nm\log nm)\)

三、总结

扫描线不只能扫下标,也能扫值域!

#include <cstdio>
#include <cassert>
#include <algorithm>
using namespace std;
const int M = 200005;
const int MOD = 998244353;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,ans,id[M],mi[4*M],cnt[4*M],tr[4*M],ad[4*M];
vector<int> a[M];int mf,num,sum;
void build(int i,int l,int r)
{
	if(l==r)
	{
		tr[i]=l;cnt[i]=1;
		return ;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	tr[i]=(tr[i<<1]+tr[i<<1|1])%MOD;
	cnt[i]=cnt[i<<1]+cnt[i<<1|1];
}
void work(int i,int c)
{
	mi[i]+=c;ad[i]+=c;
}
void down(int i)
{
	if(!ad[i]) return ;
	work(i<<1,ad[i]);
	work(i<<1|1,ad[i]);
	ad[i]=0;
}
void add(int i,int l,int r,int L,int R,int f)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		work(i,f);
		return ;
	}
	int mid=(l+r)>>1;down(i);
	add(i<<1,l,mid,L,R,f);
	add(i<<1|1,mid+1,r,L,R,f);
	mi[i]=min(mi[i<<1],mi[i<<1|1]);
	tr[i]=cnt[i]=0;
	if(mi[i]==mi[i<<1])
		tr[i]=tr[i<<1],cnt[i]=cnt[i<<1];
	if(mi[i]==mi[i<<1|1])
		tr[i]=(tr[i]+tr[i<<1|1])%MOD
		,cnt[i]+=cnt[i<<1|1];
}
void ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		if(mi[i]<mf) mf=mi[i],num=sum=0;
		if(mi[i]==mf)
			sum=(sum+tr[i])%MOD,num+=cnt[i];
		return ;
	}
	int mid=(l+r)>>1;down(i);
	ask(i<<1,l,mid,L,R);
	ask(i<<1|1,mid+1,r,L,R);
}
void upd(int t,int x,int y,int f)
{
	int b[6]={a[x][y],a[x+1][y+1],a[x+1][y],a[x][y+1]};
	sort(b,b+4);
	int ls=0,num=0;
	for(int i=3;i>=0;i--)
	{
		if(b[i]>t) continue;
		if(!b[i]) break;
		if(num==1 || num==3)
			add(1,1,k,b[i]+1,ls,f);
		num++;ls=b[i];
	}
	if(num==1 || num==3) add(1,1,k,1,ls,f);
}
signed main()
{
	freopen("pig.in","r",stdin);
	freopen("pig.out","w",stdout);
	n=read();m=read();k=n*m;
	for(int i=0;i<=n+1;i++)
		a[i].resize(m+5);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			a[i][j]=read();
			id[a[i][j]]=m*i+j-1;
		}
	build(1,1,k);
	for(int i=1;i<=k;i++)
	{
		int x=id[i]/m,y=id[i]%m+1;
		//delete
		upd(i-1,x,y,-1);upd(i-1,x-1,y,-1);
		upd(i-1,x,y-1,-1);upd(i-1,x-1,y-1,-1);
		//add
		upd(i,x,y,1);upd(i,x-1,y,1);
		upd(i,x,y-1,1);upd(i,x-1,y-1,1);
		//cal
		mf=1e9;num=sum=0;
		ask(1,1,k,1,i);
		assert(mf==4);
		ans=(ans+(i+1ll)*num-sum)%MOD;
	}
	printf("%d\n",(ans+MOD)%MOD);
}
上一篇:洛谷 P7450 - [THUSCH2017] 巧克力(斯坦纳树+随机化)


下一篇:NOIP 模拟 $84\; \rm 寻找道路$