1. 程式人生 > 其它 >P3017 [USACO11MAR]Brownie Slicing G 題解

P3017 [USACO11MAR]Brownie Slicing G 題解

CSDN同步

原題連結

這題做法還算比較明顯,\(500\) 的資料範圍也暗示了做法。

考慮直接二分所求答案,在 \(\mathcal{O}(n^2)\) 的時間內進行驗證。如何驗證 \(x\) 的合法性?

可以逐行操作。比如先考慮把第一行分成 \(\geq x\)\(b\) 塊。如果不可以,那麼就加上第二行再分,一直疊加直到可以分出這樣的 \(b\) 塊為止,假設疊加到了第 \(p\) 行,那麼 \([1,p]\) 就作為橫向切的第一刀(即切在 \(p\) 行處),然後再對 \(p+1\) 行進行同樣的操作。最後判斷能否切到 \(a\) 刀即可。這個過程做 \(n\) 次。

如何驗證能否把若干行分成 \(\geq x\)

\(b\) 塊?要求線性做法,你有可能會想到再次二分?那 \(2\)\(\text{log}\) 當然也可以。但不如考慮直接 \(1 - n\) 列舉列的切法,維護二維字首和,掃到當前和 \(\geq x\) 那麼就清空和,掃到最後一列看有沒有 \(b\) 塊。這個過程也要做 \(n\) 次。

其實說白了,就是先操作列切,合法了再操作行切,行列列舉法。加上二分,複雜度沒炸,這題就輕鬆過了。

時間複雜度:\(\mathcal{O}(n^2 \log \sum a_{i,j})\).

注意:二分不知道怎麼寫 l<rr=mid 的寫法炸了,只能換了一種。

#include<bits/stdc++.h>
using namespace std;

const int N=5e2+1;

inline int read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}

inline void write(int x) {
	if(x<0) {putchar('-');write(-x);return;}
	if(x<10) {putchar(char(x%10+'0'));return;}
	write(x/10);putchar(char(x%10+'0'));
}

int r,c,a,b,s[N][N];

inline int Sum(int a,int b,int c,int d) {return s[c][d]-s[a-1][d]-s[c][b-1]+s[a-1][b-1];}

inline bool check(int x) {
	int ans=0,st=1;
	for(int i=1;i<=r;i++) {
		int p=0,q=1;
		for(int j=1;j<=c;j++)
			if(Sum(st,q,i,j)>=x) p++,q=j+1;
		if(p>=b) st=i+1,ans++;	
	} return ans>=a;
}

int main() {
	r=read(),c=read(),a=read(),b=read();
	for(int i=1;i<=r;i++)
	for(int j=1;j<=c;j++)
		s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+read();
	int l=1,r=4000*500*500;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(check(mid)) l=mid+1;
		else r=mid-1;
	} printf("%d\n",l-1);
	return 0;
}


簡易的程式碼勝過複雜的說教。