1. 程式人生 > >Wannafly挑戰賽19:B. 矩陣(單調棧)

Wannafly挑戰賽19:B. 矩陣(單調棧)

題目描述

矩陣 M 包含 R 行 C 列,第 i 行第 j 列的值為 Mi,j
請尋找一個子矩陣,使得這個子矩陣的和最大,且滿足以下三個條件:
子矩陣的行數不能超過 X 行。
子矩陣的列數不能超過 Y 列。
子矩陣中 0 的個數不能超過 Z 個。
請輸出滿足以上條件的最大子矩陣和。

輸入描述:

第一行輸入五個整數 R,C,X,Y,Z。
接下來 N 行,每行輸入 M 個整數,第 i 行第 j 列的整數表示 Mi,j。1 ≤ R,C ≤ 500.1 ≤ X ≤ R.1 ≤ Y ≤ C.1 ≤ Z ≤ R x C.-10≤ Mi,j  ≤ 109

輸出描述:

輸出滿足以上條件的最大子矩陣和

設矩陣的大小為n*m

先n²暴力所有行的選法(注意行的長度不能超過題目要求的X)

之後求出sum[j]表示前j列所有數字之和

那麼到這就是求sum[x]-sum[y]的最大值(x∈(1, m),y∈(0, m-1),y-x<=Y)

暴力所有的x,那麼很顯然你只要找到<x的最小的y即可,也就是隻用維護一個字首最小值

但是這道題還有額外的要求:列長度不能超過Y,且0的個數不能超過Z,所以對於每個x∈(1, m),一定存在一個臨界點p,滿足矩形不能跨過第p列(這個也很容易求,略),也就是要把前面的字首和全部丟掉

那。。。這不就是單調棧的經典應用嘛,搞定

#include<stdio.h>
#include<algorithm>
using namespace std;
#define LL long long
LL a[505][505], sum[505][505], q[505], s0[505][505], ts[505], t1[505];
int main(void)
{
	LL now, ans, zero;
	int n, m, i, j, c, d, e, k, L, R;
	scanf("%d%d%d%d%d", &n, &m, &c, &d, &e);
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			scanf("%lld", &a[i][j]);
			sum[i][j] = sum[i][j-1]+a[i][j];
			s0[i][j] = s0[i][j-1];
			if(a[i][j]==0)
				s0[i][j]++;
		}
	}
	ans = 0;
	for(i=1;i<=m;i++)
	{
		for(j=i;j<=m;j++)
		{
			if(j-i+1>=d+1)
				continue;
			now = zero = 0;
			L = R = 0;
			q[R++] = 0;
			for(k=1;k<=n;k++)
			{
				now += sum[k][j]-sum[k][i-1];
				zero += s0[k][j]-s0[k][i-1];
				ts[k] = now;
				t1[k] = zero;
				while(L<R && q[L]+c<k)
					L++;
				while(L<R && zero-t1[q[L]]>e)
					L++;
				ans = max(ans, now-ts[q[L]]);
				while(L<R && ts[q[R-1]]>=now)
					R--;
				q[R++] = k;
			}
		}
	}
	printf("%lld\n", ans);
	return 0;
}
/*
3 3 100 100 2
1 0 1
0 2 0
1 0 1
*/