[HAOI2007]修筑绿化带

本文介绍了一种使用单调队列解决在大矩阵中寻找肥沃度最小小花坛问题的方法,通过预处理前缀和简化计算,再利用单调队列优化搜索过程,最终求得最优化解。

题面描述

传送门

思路

(一年出两道单调队列)

(小花坛不能在大矩阵的边上)
理解一下题意,冥冥之中看见求和,就会想到不管三七二十一先求前缀和。

for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			qr(s[i][j]),s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
	for(int i=C+1;i<n;i++)
		for(int j=D+1;j<m;j++)
			a[i][j]=s[i][j]-s[i-C][j]-s[i][j-D]+s[i-C][j-D];//小花坛
	for(int i=A;i<=n;i++)
		for(int j=B;j<=m;j++)
			b[i][j]=s[i][j]-s[i-A][j]-s[i][j-B]+s[i-A][j-B];//大矩阵

那么现在只需要在每个大矩阵中,找到肥沃值最小的小花坛做差更新答案就行了。

关键怎么在一个 A ∗ B A*B AB的大矩阵中找到肥沃度最小 C ∗ D C*D CD的小花坛。

观察发现 a [ i ] [ j ] a[i][j] a[i][j]实际上是以(i,j)为右下角的小花坛的肥沃度总和。

实际上 b [ i ] [ j ] − min ⁡ ( a [ i − A + 2 + C , i − 1 ] [ j − B + 2 + D , j − 1 ] ) b[i][j]-\min(a[i-A+2+C,i-1][j-B+2+D,j-1]) b[i][j]min(a[iA+2+C,i1][jB+2+D,j1])

那么又回归到了理想正方形了。

先处理一下 a [ i ] [ j − B + 2 + D , j − 1 ] a[i][j-B+2+D,j-1] a[i][jB+2+D,j1],为了方便起见,可以用一个 p p p数组储存值。

同时改变一下下标,使

if(j>=B-1)p[i][j+1]=a[i][q[l]];

这样统计答案的时候可以直接、高效。

得出 p p p数组之后

p [ i ] [ j ] p[i][j] p[i][j]实际上是 min ⁡ ( a [ i ] [ j − B + 2 + D , j − 1 ] ) \min (a[i][j-B+2+D,j-1]) min(a[i][jB+2+D,j1])

类似地,求出 g g g数组

g [ i ] [ j ] g[i][j] g[i][j]实际上就是 min ⁡ ( a [ i − A + C + 2 , i − 1 ] [ j − B + 2 + D , j − 1 ] ) \min(a[i-A+C+2,i-1][j-B+2+D,j-1]) min(a[iA+C+2,i1][jB+2+D,j1])

max ⁡ A ≤ i ≤ n , B ≤ j ≤ m b [ i ] [ j ] − g [ i ] [ j ] \max\limits_{A\le i\le n,B\le j \le m}b[i][j]-g[i][j] Ain,Bjmmaxb[i][j]g[i][j]即为答案。

AC code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdlib>
#define gc getchar()
using namespace std;
const int N=1e3+10;
inline void qr(int &x)
{
    x=0;int f=1;char c=gc;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
    while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
    x*=f;
}
void qw(int x)
{
    if(x<0)x=-x,putchar('-');
    if(x/10)qw(x/10);
    putchar(x%10+48);
}
int a[N][N],s[N][N],b[N][N],g[N][N],p[N][N];
int q[N],l,r;
int main()
{
	int n,m,A,B,C,D;qr(n),qr(m),qr(A),qr(B),qr(C),qr(D);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			qr(s[i][j]),s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
	for(int i=C+1;i<n;i++)
		for(int j=D+1;j<m;j++)
			a[i][j]=s[i][j]-s[i-C][j]-s[i][j-D]+s[i-C][j-D];
	for(int i=A;i<=n;i++)
		for(int j=B;j<=m;j++)
			b[i][j]=s[i][j]-s[i-A][j]-s[i][j-B]+s[i-A][j-B];
	for(int i=C+1;i<n;i++)
	{
		l=1;r=0;
		for(int j=D+1;j<m;j++)
		{
			while(l<=r&&q[l]<j-B+D+2)++l;
			while(l<=r&&a[i][j]<=a[i][q[r]])--r;
			q[++r]=j;if(j>=B-1)p[i][j+1]=a[i][q[l]];
		}
	}
	for(int j=B;j<=m;j++)
	{
		l=1;r=0;
		for(int i=C+1;i<n;i++)
		{
			while(l<=r&&q[l]<i-A+C+2)++l;
			while(l<=r&&p[i][j]<=p[q[r]][j])--r;
			q[++r]=i;if(i>=A-1)g[i+1][j]=p[q[l]][j];
		}
	}
	int ans=0;
	for(int i=A;i<=n;i++)
		for(int j=B;j<=m;j++)	
			ans=max(ans,b[i][j]-g[i][j]);
	qw(ans);puts("");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值