2015 ACM National Contest Romania G - Por Costel and the Orchard Gym - 100923G (好难的dp)

问题

给一个NxM的矩阵,找一个联通块,让这个联通块的权值最大。
有这么几个要求:
1.每一行不能间断地取点,也就是每一行必须取一段连续的区间。
2.下一行如果取了那么与上一行必须有相交的部分,也就是要联通
3.不能不取,至少取一格
( 1<=T<=4,1<=n,m<=300 ,-1e4<=a[i][j]<=1e4)

思路

首先,我们很容易想到一个dp
dp[i][j][k]表示到第i行,区间为j-k与上面联通的最大价值。
与区间[j][k]联通也就是上一行左右端点有至少有一个在[j][k]之内
这个代码比较好写,如下

for(int i=2;i<=n;i++){
            for(int j=1;j<=m;j++){
                for(int k=j;k<=m;k++){
                    for(int qq=j;qq<=k;qq++){
                        if(dp[i][j][k]<pre[i-1][qq]+sum[i][k]-sum[i][j-1]){
                            dp[i][j][k]=pre[i-1][qq]+sum[i][k]-sum[i][j-1];
                            pre[i][j]=max(pre[i][j],dp[i][j][k]);
                            suf[i][k]=max(suf[i][k],dp[i][j][k]);
                            ans=max(ans,dp[i][j][k]);
                        }
                        if(dp[i][j][k]<suf[i-1][qq]+sum[i][k]-sum[i][j-1]){
                            dp[i][j][k]=suf[i-1][qq]+sum[i][k]-sum[i][j-1];
                            pre[i][j]=max(pre[i][j],dp[i][j][k]);
                            suf[i][k]=max(suf[i][k],dp[i][j][k]);
                              ans=max(ans,dp[i][j][k]);
                        }
                    }
                    
                }
            }
        }

解释一下,pre[i][j]表示第i行,以j为右端点能联通的最大值
suf[i][j]表示第i行以j为左端点能联通的最大值.sum[i][j]表示第i行的前缀和,之前已经先处理了一下第一行,所以这段代码从第二行开始dp
我们分析一下这段代码,发现这是一个n^4的dp,对于300的数据是过不了的,考虑一下优化。
榘蒻想了很久也不会做,于是求助大佬,愤怒学习了一波代码之后,得出了以下结论。
我们只要处理出上一行对于每一个点,他能与上面联通的最大价值就ok,具体看看代码,写在注释里面了

#include <iostream>
#include<algorithm>
#include "cstring"
using namespace std;
#define ll long long
#define maxn 305
#define inf 0x3f3f3f3f
#define mod 998244353
int dp[maxn][maxn][2];
int suf[maxn];
int sum[maxn],a[maxn][maxn];
int main(){
    int t;
    freopen("livada2.in","r",stdin);
    freopen("livada2.out","w",stdout);
    scanf("%d",&t);
    while(t--){
        int n,m;
        int ans=-inf;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf("%d",&a[i][j]);
                //sum[j]=sum[j-1]+a[i][j];
            }
        }
        sum[0]=0;
        for(int i=1;i<=m;i++)sum[i]=sum[i-1]+a[1][i];
        int cur=0;
        // 先搞第一行
        for(int i=1;i<=m;i++){
            for(int j=i;j<=m;j++){
                dp[i][j][cur]=sum[j]-sum[i-1];
                ans=max(ans,dp[i][j][cur]);
            }
        }
        cur^=1;
        for(int i=2;i<=n;i++){
            //先把当前一行的东西清空,是上两行的东西,没有用
            sum[0]=0;
            for(int j=1;j<=m;j++){
                sum[j]=sum[j-1]+a[i][j];
                suf[j]=-inf;
                for(int k=1;k<=m;k++){
                    dp[j][k][cur]=-inf;
                }
            }
            //这一段是优化的关键
            for(int j=1;j<=m;j++){
                int x=-inf;
                for(int k=m;k>=j;k--){    //这边从后往前搞
                    x=max(x,dp[j][k][cur^1]);  //这句话的意思是找到上一行区间为[j,k:m>j]的联通块的最大价值,从后往前搞的意义在于[j][k-2]这个区间包含于[j][k-1]这个区间
                    suf[k]=max(x,suf[k]);  //suf[k]表示上一行能包含k这个点的区间往上的最大联通块的权值
                }
            }
            for(int j=1;j<=m;j++){
                int x=0;
                for(int k=j;k<=m;k++){
                    x=max(x,suf[k]); //在区间j-k中,x是上一行[j][k]中包含某一段的联通块最大值
                    dp[j][k][cur]=x+sum[k]-sum[j-1];// 加上这一段的区间和,ans求个max
                    ans=max(ans,dp[j][k][cur]);
                }
            }
            cur^=1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
 /*
  input
  1
 3 4
 5 -3 0 0
 -2 3 3 4
 -7 -6 4 -5
  output
  17
*/

这题感觉好难- 。- 还是要多复习复习。

全部评论

相关推荐

点赞 评论 收藏
转发
点赞 收藏 评论
分享
牛客网
牛客企业服务