天天看點

noip2014 子矩陣 (動态規劃+位運算)

P1914子矩陣

​​Accepted​​

标簽:

​​NOIP普及組2014​​

描述

給出如下定義:

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

    例如,下面左圖中選取第 2、4 行和第 2、4、5 列交叉位置的元素得到一個 2*3 的子矩陣如右圖所示。

  2. 相鄰的元素:矩陣中的某個元素與其上下左右四個元素(如果存在的話)是相鄰的。
  3. 矩陣的分值:矩陣中每一對相鄰元素之差的絕對值之和。

本題任務:給定一個 n 行 m 列的正整數矩陣,請你從這個矩陣中選出一個 r 行 c 列的 子矩陣,使得這個子矩陣的分值最小,并輸出這個分值。

格式

輸入格式

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

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

輸出格式

輸出共 1 行,包含 1 個整數,表示滿足題目描述的子矩陣的最小分值。

樣例1

樣例輸入1[複制]

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

樣例輸出1[複制]

6

樣例2

樣例輸入2[複制]

7 7 3 3

7 7 7 6 2 10 5

5 8 8 2 1 6 2

2 9 5 5 6 1 7

7 9 3 6 1 7 8

1 9 1 4 7 8 8

10 5 9 1 1 8 10

1 3 1 5 4 8 6

樣例輸出2[複制]

16

限制

對于 50%的資料,1 ≤ n ≤ 12, 1 ≤ m ≤ 12, 矩陣中的每個元素 1 ≤ a[i][j] ≤20;

對于 100%的資料,1 ≤ n ≤ 16, 1 ≤ m ≤ 16, 矩陣中的每個元素 1 ≤ a[i][j] ≤1000,1 ≤ r ≤ n, 1 ≤ c ≤ m。

時間限制:每一組測試資料1s。

提示

【輸入輸出樣例 1 說明】

該矩陣中分值最小的 2 行 3 列的子矩陣由原矩陣的第 4 行、第 5 行與第 1 列、第 3 列、 第 4 列交叉位置的元素組成,為

​675566​

,其分值為 |6 − 5| + |5 − 6| + |7 − 5| + |5 − 6| + |6 − 7| + |5 − 5| + |6 − 6| = 6。

【輸入輸出樣例 2 說明】

該矩陣中分值最小的 3 行 3 列的子矩陣由原矩陣的第 4 行、第 5 行、第 6 行與第 2 列、第 6 列、第 7 列交叉位置的元素組成,選取的分值最小的子矩陣為

​9957888810​

來源

NOIP2014 普及組

解析:

動态規劃+位運算。

         基本思路:先确定行,再對列進行動态規劃。

         1.假設 n=3,r=2,以二進制來表示每一行,則:

            0 1 1  :表示標明1、2行

            1 0 1  :表示標明1、3行

            1 1 0  :表示標明2、3行

            可以看到,所有的情況即為在長度為 n 的二進制中,存在 r 個 1 的情況。

            這就讓我想起了曾經做過的 poj2435,題意是這樣的:給定一個數 x,求與 x 在二進制下含有相同個數的 1 的數中最小的數。

           正好适合這道題。代碼見下:

                            x=n&(-n),t=n+x;

                            ans=t|((n^t)/x)>>2;

​​javascript:void(0)​​

       2.确定行,對列進行動态規劃。

         用 f[i][j] 表示以第 i 列結尾的 r 行 j 列的子矩陣的最優值,則:

           初始化 f[i][1]

           f[i][j]=max{f[k][j-1]+f[i][1]+get(k,i)}

代碼:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 16
using namespace std;

int n,m,r,c,a[maxn+5][maxn+5];
int b[maxn+5],f[maxn+5][maxn+5];
int ans=2000000000;

void work(int s)
{
  int i,j,k,x,p;
  for(b[0]=i=0;i<n;i++)
    if(s&(1<<i))b[++b[0]]=i+1;
  
  memset(f,90,sizeof(f));
  
  for(i=1;i<=m;i++)
    for(f[i][1]=0,j=2;j<=b[0];j++)
    f[i][1]+=abs(a[b[j]][i]-a[b[j-1]][i]);
  
  for(j=2;j<=c;j++)
    for(i=j;i<=m;i++)
    for(k=j-1;k<i;k++)
      {
        for(x=0,p=1;p<=b[0];p++)x+=abs(a[b[p]][k]-a[b[p]][i]);
          f[i][j]=min(f[i][j],f[k][j-1]+f[i][1]+x);
    }
  for(i=c;i<=m;i++)ans=min(ans,f[i][c]);    
}

int main()
{
  int i,j,k;
  scanf("%d%d%d%d",&n,&m,&r,&c);
  for(i=1;i<=n;i++)
    for(j=1;j<=m;j++)
      scanf("%d",&a[i][j]);
  
  int s=0,e=0,x,t;
  for(i=0;i<r;i++)s=s|(1<<i);
  for(i=n-r;i<n;i++)e=e|(1<<i);
  while(s<=e)
    {
      work(s);
      x=s&(-s),t=s+x;
      s=t|((s^t)/x)>>2;
    }  
  printf("%d\n",ans);  
  return 0;
}