洛谷題單指南-字首和差分與離散化-P3017 [USACO11MAR] Brownie Slicing G

五月江城發表於2024-07-30

原題連結: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;
}

相關文章