[洛谷P2258][NOIP2014PJ]子矩陣(dfs)(dp)

NOIP 2014普及組 T4(話說一道PJ組的題就把我卡了一個多小時誒)

這道題在我看第一次的時候是沒有意識到這是一道DP題的,然後就摁着DFS敲了好長時間,結果敲了一個TLE

這是DP!!!

下面開始進入正題

 

題目描述

 

給出如下定義:

 

  1. 子矩陣:從一個矩陣當中選取某些行和某些列交叉位置所組成的新矩陣(保持行與列的相對順序)被稱爲原矩陣的一個子矩陣。

 

例如,下面左圖中選取第22、44行和第22、44、55列交叉位置的元素得到一個2 \times 32×3的子矩陣如右圖所示。

 

9 3 3 3 9

 

9 4 8 7 4

 

1 7 4 6 6

 

6 8 5 6 9

 

7 4 5 6 1

 

的其中一個2 \times 32×3的子矩陣是

 

4 7 4

 

8 6 9

 

  1. 相鄰的元素:矩陣中的某個元素與其上下左右四個元素(如果存在的話)是相鄰的。

  2. 矩陣的分值:矩陣中每一對相鄰元素之差的絕對值之和。

 

本題任務:給定一個nn行mm列的正整數矩陣,請你從這個矩陣中選出一個rr行cc列的子矩陣,使得這個子矩陣的分值最小,並輸出這個分值。

 

輸入輸出格式

輸入格式:

 

第一行包含用空格隔開的四個整數n,m,r,cn,m,r,c,意義如問題描述中所述,每兩個整數之間用一個空格隔開。

 

接下來的nn行,每行包含mm個用空格隔開的整數,用來表示問題描述中那個nn行mm列的矩陣。

輸出格式:

 

一個整數,表示滿足題目描述的子矩陣的最小分值。

樣例:
輸入

5 5 2 3
9 3 3 3 9
9 4 8 7 4
1 7 4 6 6
6 8 5 6 9
7 4 5 6 1

輸出

6

 

這道題就是將題目所給你的矩陣進行“選取”行與列的操作,從而得到所求的最大值的集合,但是重要的是我們不知道題目所給的行和列的值是多少,因此我們可以對此進行一個循環中的判斷操作,在選出來這個行之後再去考慮列的情況,從而得出最優決策。

這個地方還有一個降維操作,就是我們在一個r * m(事先以及決定了選取那幾行,該去考慮列的問題時)的矩陣中選取c列,這個時候我們可以把二維降低到一維,(因爲這其中行已經有了判斷的標準,我們只需要進行列的操作就足夠了).

下面開始講DP過程:

我們設f[i][j]表示在這個r*m的矩陣中,在其前i列中選擇j列(且選的列中包括第i列),組成的子矩陣中,最小值(即其相鄰元素的差的絕對值的和的最小值(之後的值等表達也是指的這個東西,即題目要求求出的值))是多少。

這樣,推出狀態轉移方程如下:

f[i][j] = min (f[k][j-1] + hc[i][k] + lc[i])

下面就都是一些細節上的優化,代碼裏面已經講的很清楚了。

Code:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,r,c,gs=1,minn=0x7fffffff,cmin;  //不可能選取0列 
int a[50][50];
int ch[50],hc[50][50],lc[50];  //hc[i][j] 對於第i列與第j列之間,所有同一行元素的差的絕對值的和
                               //lc[i]指的是第i列中所有元素的值
                               //ch[]是在dfs過程的數組 
int f[50][50];
void init()  //初始化 
{
    for(int i=1;i<=m;i++)
    {
        lc[i] = 0;
        for(int j=1;j<r;j++)
        lc[i] += abs(a[ch[j]][i] - a[ch[j + 1]][i]);  //注意是+= 
    }
    for(int i=2;i<=m;i++)
    {
        for(int j=1;j<i;j++)  //注意j<i 
        {
            hc[i][j] = 0;  //注意初始化 
            for(int k=1;k<=r;k++)
            hc[i][j] += abs(a[ch[k]][i] - a[ch[k]][j]);  //注意是+= 
        }
    }
}
void dp()
{
    for(int i=1;i<=m;i++)
    {
        cmin = min(i,c);
        for(int j=1;j<=cmin;j++)
        {
            if(j == 1)  //這個邊界是隻選取一列,就把元素賦進去就好 
            f[i][j] = lc[i];
            else
            if(j == i)  //這個邊界是前i列都需要選取 
            f[i][j] = f[i - 1][j - 1] + lc[i] + hc[i][j - 1];
            else
            {
                f[i][j] = 0x7fffffff;  //注意初始化 
                for(int k=j-1;k<i;k++)
                f[i][j] = min(f[i][j],f[k][j - 1] + lc[i] + hc[i][k]);  //取最小值 
            }
            if(j == c)  //如果這種狀態存在,那我們就更新一下 
            minn = min(minn,f[i][c]);
        }
    }
}
void dfs(int rt)
{
    if(rt > n)  // 
    {
        init();
        dp();
        return ;
    }
    if(r - gs + 1 == n - rt + 1)  //敲黑板!
    /*
    這個地方是一個剪枝,主要優化在了果rt和rt以後的元素必須全部取完,才能滿足剛好有r個的條件,
    則必須rt,否則便會取到少於r個元素的情況。
    這樣我們保證了rt > n時所有情況都剛好有r個,
    */
    {
        ch[gs++] = rt;  //注意是gs++而不是++gs 
        dfs(rt + 1);
        ch[gs--] = 0;  //注意是gs--而不是--gs 
        return ;
    }
    dfs(rt + 1);
    if(gs <= r)  //如果已經選取滿了 
    {
        ch[gs++] = rt;  //但是還是需要這一步 
        dfs(rt + 1);
        ch[gs--] = 0;
    }
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&r,&c);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    scanf("%d",&a[i][j]);  //輸入初始矩陣 
    dfs(1);  //開始搜索 
    printf("%d",minn);  //答案已經被更新,輸出就行 
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章