城壁 (Rampart)

题意简述

  • 给定一张 $H \times W $ 的网格图,其中有 \(P\) 个被标记的点,求边长为 \(L\) 或以上的正方形的个数,要求正方形的边不得经过被标记的点。

  • \(1 \le H,W \le 4000\), \(1 \le P \le 10^5\)

分析与解答

考虑枚举合法正方形的左下端点 \((i,j)\) 和正方形边长 \(len\) ,由此可以得到右上端点 \((i+len-1,j+len-1)\)

考虑什么条件下枚举到的正方形才能被计入答案。

\(left,right,up,down\) 数组,分别表示点 \((i,j)\) 向左/右/上/下最多能延伸多少长度,并且不遇到被标记的点。

\(left,right,up,down\) 数组可以通过 \(\Theta(nm)\) 递推得到。

\(lu_{i,j}=\min(light_{i,j},up_{i,j})\), \(rd_{i,j}=\min(right_{i,j},down_{i,j})\)
\(x = i+len-1\)\(y=j+len-1\)

那么必须当 \(lu_{i,j} \ge len\)\(rd_{x,y} \ge len\)\(len \ge L\) 时,这个正方形才能被计入答案。

也就是说对于 \((i,j)\),我们需要求出有多少个点 \(i+len-1\), \(j+len-1\) 满足 \(rd_{i+len-1,j+len-1} \ge len\)

这个问题就可以用主席树解决。

时间复杂度 \(\Theta(nm \log_2 (\max(n,m)))\)

Code

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

typedef long long ll;
const int MAXN = 4010;

int n, m, l, k, a[MAXN][MAXN];
int ld[MAXN][MAXN], rd[MAXN][MAXN], ud[MAXN][MAXN], dd[MAXN][MAXN];

struct node{
	int lc, rc, val;
}t[MAXN*40];
int rt[MAXN], cnt = 0;
void update(int l, int r, int &now, int ver, int pos)
{
	t[now = ++cnt] = t[ver];
	++t[now].val;
	if(l == r) return ;
	int mid = l+r>>1;
	if(pos <= mid) update(l, mid, t[now].lc, t[ver].lc, pos);
	else update(mid+1, r, t[now].rc, t[ver].rc, pos);
}
int query(int l, int r, int x, int y, int ql, int qr)
{
	if(l >= ql && r <= qr) return t[y].val - t[x].val;
	int mid = l+r>>1;
	int res = 0;
	if(ql <= mid) res += query(l, mid, t[x].lc, t[y].lc, ql, qr);
	if(qr >= mid+1) res += query(mid+1, r, t[x].rc, t[y].rc, ql, qr);
	return res;
}

int d1[MAXN], d2[MAXN], tot;

inline int calc()
{
	const int maxn = max(n, m);
	cnt = 0;
	for(int i=1;i<=tot;i++)
		update(-maxn, maxn, rt[i], rt[i-1], d2[i]-i-1);
	int res = 0;
	for(int i=1;i<=tot;i++)
	{
		int nl = i+l-1, nr = i+d1[i]-1;
		if(nl > nr) continue;
		res += query(-maxn, maxn, rt[nl-1], rt[nr], -i, maxn);
	}
	return res;
}

int main()
{
	scanf("%d%d%d%d",&n,&m,&l,&k);
	for(int i=1;i<=k;i++)
	{
		int x, y;
		scanf("%d%d",&x,&y);
		a[x][y] = 1;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			ld[i][j] = a[i][j]?0:ld[i][j-1]+1;
			ud[i][j] = a[i][j]?0:ud[i-1][j]+1;
		}
	for(int i=n;i>=1;i--)
		for(int j=m;j>=1;j--)
		{
			rd[i][j] = a[i][j]?0:rd[i][j+1]+1;
			dd[i][j] = a[i][j]?0:dd[i+1][j]+1;
		}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			a[i][j] = 0;
	ll ans = 0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(a[i][j]) continue;
			int x = i, y = j;
			tot = 0;
			while(x <= n && y <= m)
			{
				a[x][y] = 1;
				d1[++tot] = min(rd[x][y], dd[x][y]);
				d2[tot] = min(ld[x][y], ud[x][y]);
				++x, ++y;
			}
			ans += calc();
		}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2022-03-26 10:19  Aphrosia  阅读(60)  评论(0编辑  收藏  举报