動態規劃題解 D001 分田地

題目解讀

原題鏈接: 牛客網在線編程題 2017年校招專題

題目描述
牛牛和 15 個朋友來玩打土豪分田地的遊戲,牛牛決定讓你來分田地,地主的田地可以看成是一個矩形,每個位置有一個價值。分割田地的方法是橫豎各切三刀,分成 16 份,作爲領導幹部,牛牛總是會選擇其中總價值最小的一份田地, 作爲牛牛最好的朋友,你希望牛牛取得的田地的價值和儘可能大,你知道這個值最大可以是多少嗎?

輸入描述:
每個輸入包含 1 個測試用例。每個測試用例的第一行包含兩個整數 n 和 m(1 <= n, m <= 75),表示田地的大小,接下來的 n 行,每行包含 m 個 0-9 之間的數字,表示每塊位置的價值

輸出描述:
輸出一行表示牛牛所能取得的最大的價值。

示例1
輸入
4 4
3332
3233
3332
2323

輸出
2

題意理解

一句話說明,給出一個矩陣,將這個矩陣切割成16個小矩形(橫向切三次,縱向切三次),得到的目標是16個小矩形中值最小的一個矩形,求在不同切法中這個最小矩形的最大值。注意示例中給出的是一個方陣,但事實上矩陣不一定是長寬相等的。

算法分析

這是一個較爲典型的最小值裏求最大的問題。初看確實沒有什麼思路,最容易的想法當然是暴力搜索,但很顯然會超時,時間複雜度達到O(n^6);這個時候,當正向找不到思路的時候,我們開始考慮反向的想法:不管最小矩陣的值是多少,它始終是存在一個範圍的,即介於0-sum[n][m]。這裏的sum[n][m]是指以(0,0)爲矩陣左上角座標,(n,m)爲矩陣右下角座標的矩陣值,也即最大的矩陣的值之和。那麼,我們是不是可以猜測一個值value,使得value處理0-sum[n][m]內,這個value值就代表了我們能夠得到的最小矩陣的最大值。在這個基礎上,問題就轉換爲了能不能找到這樣一種切法,使得切出的所有子矩陣的值都大於等於value。
由上面的分析可以看到,整個問題可以分解爲兩個階段:
第一階段:確定value值
value值的確定相當於是在0-sum[n][m]這個區間內進行搜索。通過遍歷進行搜索會出現超時錯誤,因此想到進行二分查找。isValid()函數的作用是判斷在當前這個value值的情況下是否能找到合適的切法。設置L初始爲0,R初始爲sum[n][m],mid爲(L+R)/2。有一個合理的理解爲,當value值足夠小時,肯定能找到合適的切法,比如極端情況下,當設置value=0時,不管怎麼切都是可以的。因此isValid()爲假只可能出現在value值偏大的情況下。在這種情況下,二分函數的寫法可以寫爲:

    while(l<=r){
        mid = (l+r)/2;
        if(isValid(mid)){
            l = mid+1 ;
            ans = mid ;
        }
        else{
            r = mid-1 ;
        }
    }

第二階段:確定在當前value值下是否有合適的切法
在value值下尋找切割方法較爲暴力,即首先利用遍歷的方法尋找行的三個切割點,再在三個切割點的情況下,選擇遍歷列,遍歷列的時候採用了貪心的思想。即當找到第一個可行的切割點時,就從這個切割點進行切割,以此向後類推:
設置cnt來記錄某一列是否符合條件;對於每一次的列切割結束之後,我們需要統計,如果cnt的值大於等於4,則直接返回true。當遍歷結束所有的情況後仍然沒有返回true後,即返回false。
注意這裏爲什麼將cnt的值設置爲4,這是因爲需要劃分爲16塊,且在行上切三次,列上切三次,切三次意味着列被分爲4列,故cnt的值被設置爲4。

疑問:爲什麼這個地方對列進行處理的時候可以使用貪心算法?這樣會導致比如說某一類的情況被忽略掉嗎?這個問題我還沒有想明白。

代碼:

#include<iostream>
#include<stdio.h>
#include<string.h>

using namespace std ;

int n,m ;            //n代表行、m代表列
int a[100][100];     //記錄矩陣
int sum[100][100];   //記錄矩陣中各個子矩陣的和

//給定左上角和右下角的座標值,計算這個矩陣內的所有元素的值
int calc(int x1,int y1,int x2,int y2)
{
    return (sum[x2][y2]-sum[x2][y1]-sum[x1][y2]+sum[x1][y1]);
}

int isValid(int k)
{
    for(int x1=1;x1<=n-3;x1++){
        for(int x2=x1+1;x2<=n-2;x2++){
            for(int x3=x2+1;x3<=n-1;x3++){
                int cnt = 0; //用來進行計數的變量
                int rec = 0;
                for(int y=1;y<=m;y++){
                    if(calc(0,rec,x1,y)>=k && calc(x1,rec,x2,y)>=k && calc(x2,rec,x3,y)>=k && calc(x3,rec,n,y)>=k){
                        cnt++;
                        rec = y;
                    }
                }
                if(cnt>=4){
                    return 1 ;
                }
            }
        }
    }
    return 0 ;
}

int main()
{
    scanf("%d%d",&n,&m);
    char input[100];
    //由於輸入過程中數字之間沒有空格,因此採用字符串的形式讀入
    for(int i=1;i<=n;i++){
        scanf("%s",input+1);
        for(int j=1;j<=m;j++){
            a[i][j] = input[j]-'0';
        }
    }
    //sum矩陣的初始化
    memset(sum,0,sizeof(sum)) ;
    //計算各個小矩陣的值
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+a[i][j];
        }
    }
    int l = 0;
    int r = sum[n][m];
    int mid;
    int ans;
    //採用二分查找進行搜索,關鍵的核心在於isValid()函數
    while(l<=r){
        mid = (l+r)/2;
        if(isValid(mid)){
            l = mid+1 ;
            ans = mid ;
        }
        else{
            r = mid-1 ;
        }
    }
    printf("%d\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章