NOIP 2014普及組 T4(話說一道PJ組的題就把我卡了一個多小時誒)
這道題在我看第一次的時候是沒有意識到這是一道DP題的,然後就摁着DFS敲了好長時間,結果敲了一個TLE
這是DP!!!
下面開始進入正題
題目描述
給出如下定義:
- 子矩陣:從一個矩陣當中選取某些行和某些列交叉位置所組成的新矩陣(保持行與列的相對順序)被稱爲原矩陣的一個子矩陣。
例如,下面左圖中選取第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
-
相鄰的元素:矩陣中的某個元素與其上下左右四個元素(如果存在的話)是相鄰的。
-
矩陣的分值:矩陣中每一對相鄰元素之差的絕對值之和。
本題任務:給定一個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; }