Luogu 1437 [HNOI2004]敲磚塊 (動態規划)


Luogu 1437 [HNOI2004]敲磚塊 (動態規划)

Description

在一個凹槽中放置了 n 層磚塊、最上面的一層有n塊磚,從上到下每層依次減少一塊磚。每塊磚都有一個分值,敲掉這塊磚就能得到相應的分值,如下圖所示。
14 15 4 3 23
33 33 76 2
2 13 11
22 23
31
如果你想敲掉第 i 層的第j 塊磚的話,若i=1,你可以直接敲掉它;若i>1,則你必須先敲掉第i-1 層的第j 和第j+1 塊磚。你現在可以敲掉最多 m 塊磚,求得分最多能有多少。

Input

輸入文件的第一行為兩個正整數 n 和m;接下來n 行,描述這n層磚塊上的分值a[i][j],滿足0≤a[i][j]≤100。對於 100%的數據,滿足1≤n≤50,1≤m≤n*(n+1)/2;

Output

輸出文件僅一行為一個正整數,表示被敲掉磚塊的最大價值總和。

Sample Input

4 5
2 2 3 4
8 2 7
2 3
49

Sample Output

19

Http

Luogu:https://www.luogu.org/problem/show?pid=1437

Source

動態規划

解決思路

這是一道非常難想到的動態規划問題。
首先我們把矩陣左對齊,但發現如果我們直接在這個上面對行進行轉移,我們發現是不滿足轉移的有序性的,因為第i行第j列是否可以敲掉取決於上面一個倒三角是否被敲掉,比如說這個圖

 1  2  3  4  5
 6  7  8  9
10 11 12
13 14
15

如果我們要敲掉7,則1,2都要敲掉。如果我們要敲掉12,則1,2,3,7,8都要敲掉。這給轉移帶來了麻煩。
如何解決呢?我們發現如果第i行第j列被敲掉了,那么要求對於\(\forall k \in [1,i-1]\),[k][j]一定被打掉了。
於是我們就想到話說這怎么想到的?把矩陣翻折過來。上面的矩陣就變成了這個樣子

1
2 6 
3 7 10
4 8 11 13
5 9 12 14 15

簡單點來說,就是把矩陣沿主對角線翻折,再向下對齊
這個翻轉用程序表示就是:

for (int j=1;j<=n;j++)
		for(int i=j;i<=n;i++)
			Mat_new[i][j]=read();//read就是按照原矩陣的順序從上至下從左至右讀入

那么我們就可以知道,如果要選擇[i][j],那么[i][1~(j-1)]是一定要選的,並且我們還發現,原來的選一個數需要選擇的上三角變成了更好處理的下三角。舉個例子,比如說12
在原來的圖中

 1   2   [3] [4] [5]
 6   7   [8] [9]
10  11  [12]
13 14
15

把圖翻折后

 1
 2   6 
[3]  7   10
[4] [8]  11  13
[5] [9] [12] 14 15

所以,我們設F[i][j][k]表示在新圖中的第i行取前j個總共取了k個的最大的,那么我們只要枚舉上一行是由那個轉移過來的。
可以注意到,因為我們當前是在第j列,那么我們在上一行的枚舉就至少得從j-1列開始。比如上面這個12的例子,那么就至少得從數字8(第2列)開始枚舉,枚舉到數字13(第4列)
所以我們就可以的得到動態轉移方程(Arr就是我們翻轉后的矩陣)

\[F[i][j][k]=max(F[i][j][k],F[i-1][p][k-j]+\sum_{l=1}^{l<=j}Arr[i][j])\{p \in [1,j-1]\} \]

我們發現這個算法還有改進的余地,就是利用前綴和來優化\(\sum_{l=1}^{l<=j}Arr[i][j]\)的求解。
\(Sum[i][j]=\sum_{k=1}^{k<=i}Arr[i][k]\),那么有

\[Sum[i][j]=Sum[i][j-1]+Arr[i][j] \]

所以最終的轉移方程就是

\[F[i][j][k]=max(F[i][j][k],F[i-1][p][k-j]+Sum[i][j]) \]

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

#define ll long long
#define mem(Arr,x) memset(Arr,x,sizeof(Arr))

const int maxN=60;
const int maxM=4000;
const int inf=2147483647;

int n,m;
int Arr[maxN][maxN];
int Sum[maxN][maxN];
int F[maxN][maxN][maxM];

int read();
void outp();

int main()
{
	n=read();
	m=read();
	for (int j=1;j<=n;j++)//輸入,同時轉置矩陣
		for(int i=j;i<=n;i++)
			Arr[i][j]=read();
	for (int i=1;i<=n;i++)//計算前綴和
		for (int j=1;j<=i;j++)
			Sum[i][j]=Sum[i][j-1]+Arr[i][j];
	mem(F,-1);//置為-1,標記為不行
	for (int i=1;i<=n;i++)//動態轉移初始值
	{
		F[i][0][0]=0;
		F[i][1][1]=Arr[1][i];
	}
	int Ans=0;
	for (int i=1;i<=n;i++)
		for (int j=0;j<=i;j++)
			for (int k=0;k<=m;k++)
			{
				if (j<=k)//只用j<k的時候才能推
					for (int p=max(j-1,0);p<=i-1;p++)//注意這里的取max,因為j為0的時候j-1是負數
						if (F[i-1][p][k-j]!=-1)
							F[i][j][k]=max(F[i][j][k],F[i-1][p][k-j]+Sum[i][j]);
				Ans=max(Ans,F[i][j][k]);//取最大值
			}
	printf("%d\n",Ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

int read()
{
	int x=0;
	int k=1;
	char ch=getchar();
	while (((ch>'9')||(ch<'0'))&&(ch!='-'))
		ch=getchar();
	if (ch=='-')
	{
		k=-1;
		ch=getchar();
	}
	while ((ch>='0')&&(ch<='9'))
	{
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*k;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM