zoukankan      html  css  js  c++  java
  • 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
    ",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;
    }
    
  • 相关阅读:
    hdu 1399(水题)
    hdu 1252(BFS)
    sm3算法的简单介绍
    undefined reference to 问题汇总及解决方法 ----- 还有一种问题没有解决(可能是顺序问题)
    OpenSSL之X509系列
    RSA key lengths
    进程间通信(IPC)介绍
    判断主机、网络字节序和互相转换
    整理struct sockaddr和struct sockaddr_in
    valgrind 的使用简介
  • 原文地址:https://www.cnblogs.com/SYCstudio/p/7440920.html
Copyright © 2011-2022 走看看