专栏文章

题解:P6100 [USACO19FEB] Painting the Barn G

P6100题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@mipxbyf1
此快照首次捕获于
2025/12/03 19:28
3 个月前
此快照最后确认于
2025/12/03 19:28
3 个月前
查看原文

题解:P6100 [USACO19FEB] Painting the Barn G

Preface

好题呀,一九年金组就有这么难了?

Problem Statement

Solution

首先有一条非常重要的限制,即两个矩阵不能相交,这就说明对于每一格,我们最多在原矩阵的基础上加
有了上面这条限制我们就能发现其实我们关心的只有原本就有 KK 层的,和原本有 K1K - 1 层的格子,前者被覆盖到会使得答案减一,而后者加一。
这时我们就能想到下面的一种思路。由于矩阵不能相交,我们枚举第一个矩阵的右下角,记录所有左上角坐标不会导致相交的第二个矩阵中最优的一个的贡献,加上对于此时的右下角,最优的第一个矩阵的贡献,就是这个右下角的答案,打擂台,求出最终答案。
此时我们就需要一个算法,求出以任意点为左上角/右下角的最大子矩阵。
可以用 dp 实现,过程很像最大字段和,下面我们用找对于一个固定右下角的最大子矩阵为例。
为了达到 n3n^3 而不是 n4n^4 复杂度,我们只枚举想要的右下角,以及最优矩阵的左上角的 x 坐标,将满足要求的最优矩阵存为 fi,j,kf_{i,j,k}i,ji, j 表示右下角,kk 表示左上角的 x 坐标)
转移过程和最大字段和很像,每次决策是否将这最后以行接在上一行后面,还是这单独一行另起一个矩阵。
fi,j,k=max(fi,j,k1,0)+vali,jvalk1,j\begin{aligned} f_{i, j, k} = \max(f_{i, j, k - 1}, 0) + val_{i, j} - val_{k - 1, j} \end{aligned}
vali,jvalk1,jval_{i, j} - val_{k - 1, j} 表示的是最后一行的值,valval是一个预处理好的前缀和。
有了最大子矩阵接下来的过程就比较简单了,细节代码里有很多注释。

Code

我代码里写的稍微有些不同,最后一步枚举的不是第一个的右下,而是第二个的左上,但是过程基本一样。
CPP
/*
最大子矩阵 is acutally a little more complicated then I thought,
we need 2 dps for max sub matrix with a fixed bottom right,
and 2 more for such with fixed top left

let's use bottom right as example

first for max subarray(substring), each time the decision we make
with dp is whether to connect with last i - 1, or to start new.

similar here, this time however, 2 dimentional

the key to achieving n^3 not n^4 complexity is that we only
enumerate the coordinate of bottom right(duh, we need that), and
the x(or y if you want) coordinate of the top left of the max
submatrix. what about y? well now we get to use dp and just
decide whether we connect this last row to the matrix above or
just to start a new one with height 1(just this row).

be careful though we need 2 dp arrays, one 3d and one 2d, cuz
2d store answer, but then actually transfering we need to
restrict which j we transfer from, which need to be specified in
the state.(see details in code)also I don't actually have to make
it 3d cuz i can just put j as outer most layer loop. but you
know, we've got space!

brm[i][j] = the max br[k][l] for all k <= i and l <= j
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, k, dif[205][205], pre[205][205];
int ans, orig, op1[205][205], op2[205][205], cnt, chec;
int brf[205][205][205], tlf[205][205][205];
int br[205][205], tl[205][205], brm[205][205];
//这里的一大堆数组,brf就是题解里的f数组,br是对于一对 i, j 所有
//k 找到最优的后存起来了,tl同理,只不过是左上角
//brm 则是在最后需要找到在一个点一下的所有 br 里最大的一个
//相当于一个二维后缀最大值 
int mx, my;
signed main(){
	cin>>n>>k;
	for(int i = 1; i <= n; i++){
		int x, y, x2, y2;
		cin>>x>>y>>x2>>y2;
		x++, y++, x2++, y2++;
		dif[x][y]++;
		dif[x][y2]--;
		dif[x2][y]--;
		dif[x2][y2]++;
		mx = max(mx, x2);
		my = max(my, y2);
		//start from 1 instead of 0, so we don't have RE
	}
	mx = min(mx, 200LL);
	my = min(my, 200LL);
	//MAJOR MAJOR BUG!!!!!!!!
	//cuz our br will consider the border cell painted, but cells
	//at 200(or 201 now that we've added 1) doesn't exist! it's
	//just a line, there's no cells behind the line
	//this stuck me for 50 minutes, and even after discovering
	//this bug I wrote max() instead of min, and stuck me another
	//10 minutes.
	for(int i = 1; i <= mx; i++){
		for(int j = 1; j <= my; j++){
			pre[i][j] = dif[i][j];
			pre[i][j] += pre[i - 1][j]+pre[i][j-1]-pre[i-1][j-1];
			if(pre[i][j] == k)orig++;
		}
	}
	//found prefix sum(silver level end here)
	//ans right now is the answer for adding no rectangles
	//one dimentional prefix sum TOP TO BOTTOM!!!!
	for(int i = 1; i <= mx; i++){
		for(int j = 1; j <= my; j++){
			op1[i][j] = op1[i - 1][j] + (pre[i][j] == (k - 1));
			op1[i][j] -= (pre[i][j] == (k));
		}
	}
	for(int i = 1; i <= mx; i++){
		for(int j = 1; j <= i; j++){
			for(int k = 1; k <= my; k++){
				//bottom right corner is i, k
				int lyc = max(brf[i][j][k - 1], 0LL);
				//last y coordinate(when y was k - 1)
		/*
		if(lyc+op1[i][k]-op1[j-1][k] == 7 && i == mx && k == 3){
			cout<<mx<<" "<<j<<endl;
		}
				//bro above was actually a bug cuz I forgot to
				//comment it off, like out of all these years
				//of programming, this?!?!?!
				*/
				brf[i][j][k] = lyc + op1[i][k] - op1[j - 1][k];
				//we still enumerate the x coordinate of the
				//top left of the best rectange with bottom right
				//i k, but we don't need to enumerate the y
				//coordinate cuz we use dp
				br[i][k] = max(br[i][k], brf[i][j][k]);
				//we had to add a dimension for brf cuz other
				//wise we might choose an L shape around the
				//negatives, so we have to limit the js we can
				//transfer brf from
			}
		}
	}
	//now, we've (finally) found the biggest rectangle with
	//bottom right at i, j, stored in br[i][j]
	//this time BOTTOM TO TOP!!!!
	for(int i = mx; i >= 1; i--){
		for(int j = 1; j <= my; j++){
			op2[i][j] = op2[i + 1][j];
			op2[i][j] += (pre[i][j] == (k - 1));
			op2[i][j] -= (pre[i][j] == (k));
		}
	}
	for(int i = mx; i >= 1; i--){
		for(int j = mx; j >= i; j--){
			for(int k = my; k >= 1; k--){
				int lyc = max(tlf[i][j][k + 1], 0LL);
				//this is actually basically the decision making
				//process of dp
				tlf[i][j][k] = lyc + op2[i][k] - op2[j + 1][k];
				tl[i][k] = max(tl[i][k], tlf[i][j][k]);
			}
		}
	}
	for(int i = 1; i <= mx; i++){
		for(int j = 1; j <= my; j++){
			brm[i][j] = br[i][j];			
			brm[i][j] = max(brm[i][j - 1], brm[i][j]);
			brm[i][j] = max(brm[i - 1][j], brm[i][j]);
		}
	}
	ans = orig;
	for(int i = 1; i <= mx; i++){
		for(int j = 1; j <= my; j++){
			//i, j as the top left of the second rectangle
			ans = max(ans, orig + tl[i][j]);
			ans = max(ans, orig + br[i][j]);
			int mabr = max(brm[i - 1][my], brm[mx][j - 1]);
			//don't forget to do i - 1 and j - 1 not i and j, cuz
			//both br and tl claim the block at their border
			//if we do i,j we have a row overlapping
			ans = max(ans, orig + mabr + tl[i][j]);
		}
	}
	cout<<ans<<endl;
	return 0;
}

After thought

好题呀,建议升紫。
求赞。

评论

1 条评论,欢迎与作者交流。

正在加载评论...