原題連結:https://www.luogu.com.cn/problem/P3017
題意解讀:將一個r*c的矩陣,橫向切成a條,每一條縱向切除b塊,計算每一塊子矩陣之和的最小值最大是多少。
解題思路:
要計算最小值中最大的,直覺上可以採用二分,下面來分析單調性:
給定一個子矩陣塊之和的值,值越小可以劃分的條數、塊數就越多,因此具備單調性。
因此,可以二分這個子矩陣塊之和的最小值,然後檢查是否可以劃分成至少a條,每條至少b塊,
如果可以,證明這個值還可以更大;如果不可以,證明這個值需要更小。最後得到的答案即最小值中最大的。
關鍵問題在於如何check,已知最小矩陣塊之和x,計算是否能分成至少a條、每條至少b塊?
只需要列舉每一行i、再對每一行列舉每一列j
記錄新的一條的起始位置new_row,新的一列的起始位置new_col
利用字首和,計算當前子矩陣的和s[i][j] - s[new_row - 1][j] - s[i][new_col - 1] + s[new_row - 1][new_col - 1]
只要子矩陣的和剛好大於等於x,證明可以在j處切一塊,當前條的總塊數累加,並更新下一塊的起始位置
如果當前條總塊數大於等於b,證明可以開始劃分下一條,總條數累加,更新下一條的起始位置
如果總條數大於等於a,說明可以劃分成功,返回true。
100分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 505;
int w[N][N], s[N][N];
int r, c, a, b;
bool check(int x)
{
int rows = 0, new_row = 1; //rows一共切了多少條,new_row新的一條從哪裡開始
for(int i = 1; i <= r; i++)
{
int cols = 0, new_col = 1; //cols當前條一共切了多少塊,new_col新的一塊從哪裡開始
int sum = 0; //當前塊的和
for(int j = 1; j <= c; j++)
{
int kuai = s[i][j] - s[new_row - 1][j] - s[i][new_col - 1] + s[new_row - 1][new_col - 1];
if(kuai >= x) //貪心找到一個大於等於x的塊
{
cols++; //當前條切一塊
new_col = j + 1; //更新新的一塊開始位置
}
}
if(cols >= b)
{
rows++;
new_row = i + 1; //更新新的一條開始位置
}
}
return rows >= a;
}
int main()
{
cin >> r >> c >> a >> b;
for(int i = 1; i <= r; i++)
{
for(int j = 1; j <= c; j++)
{
cin >> w[i][j];
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + w[i][j];
}
}
int left = 0, right = s[r][c], ans = -1;
while(left <= right)
{
int mid = left + right >> 1; //二分巧克力屑數最少的塊的值
if(check(mid))
{
ans = mid;
left = mid + 1;
}
else right = mid - 1;
}
cout << ans;
return 0;
}