• 简单区间dp


    题目链接

    对于基本区间dp,设dp[l][r]是区间l到r的最大价值。

    我们可以枚举区间的长度,在枚举左端点,判断即可。

    当右端点大于n,就break。

    dp[l][r]=max(dp[l+1][r]+v[l]*(n-i+1),dp[l][r-1]+v[r]*(n-i+1))

    别忘了初始化,dp[i][i]=n*v[i].

    代码:

     1 #include<cstdio>
     2 #include<iostream>
     3 using namespace std;
     4 int dp[3000][3000];
     5 int n,v[3000];
     6 int main(){
     7     scanf("%d",&n);
     8     for(int i=1;i<=n;++i)
     9         scanf("%d",&v[i]);
    10     for(int i=1;i<=n;++i)dp[i][i]=n*v[i];
    11     for(int i=2;i<=n;++i){
    12         for(int l=1;l<=n;l++){
    13             int r=l+i-1;
    14             if(r>n)break;
    15             dp[l][r]=max(dp[l+1][r]+v[l]*(n-i+1),dp[l][r-1]+v[r]*(n-i+1));
    16         }
    17     }printf("%d
    ",dp[1][n]);
    18     return 0;
    19 }
    View Code

     题目链接

    也可以用区间dp做。

    设dp[i][j]是区间[i,j]的最佳答案。

    我们枚举区间。

    对于每次区间,枚举合并的交点。

    当且仅当合并两方值相等时才能合并。

    if(dp[i][k]==dp[k+1][j])dp[i][j]=max(dp[i][j],dp[i][k]+1)

    代码:

    #include<cstdio>
    #include<iostream>
    using namespace std;
    int n,dp[500][500],ans=-(1<<30);
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;++i)scanf("%d",&dp[i][i]);
        for(int i=n-1;i>=1;--i){
            for(int j=i+1;j<=n;++j){
                for(int k=i;k<j;++k)
                    if(dp[i][k]==dp[k+1][j])//可以合并 
                        dp[i][j]=max(dp[i][j],dp[i][k]+1);//dp[i][k]&dp[k+1][j]'s merge
                ans=max(ans,dp[i][j]);//max
            }
        }printf("%d
    ",ans);
        return 0;
    } 
    View Code

     题目链接

    本题与上一题类似,发现样例都一样。

    但是神奇的数据范围不得不让我们重新考虑转移方程。

    一看每个数都小于等于40,那么很容易想到dp中的一维表示合并的数。

    看题解后恍然大悟,设dp[i][j]是能够合并成i且左端点为j的右端点的位置。

    那么,dp[i][j]=dp[i-1][dp[i-1][j]]

    即,当一个数可以合并成i时,一定是由i-1合并出来的。

    我们画一个数轴,其中j在左边。

    那么,能合并出i-1的位置就在dp[i-1][j]

    那么继续往后,在合并出一个i-1,那么这个位置显然是:

    dp[i-1][dp[i-1][j]]

    至此,得到转移方程。

    那么,表示数字时,我们只需要开到58.

    首先,因为数字小于等于40.其次,

    262144=218.

    用类似倍增的思想,来倍增2.

    于是我们要开到58.(也可以用运气法开个大的)

    代码:

     1 #include<cstdio>
     2 #include<iostream>
     3 #define MAXN 262144+100
     4 using namespace std;
     5 int n,a[MAXN],ans;
     6 int dp[59][MAXN];
     7 int main(){
     8     scanf("%d",&n);
     9     for(int i=1;i<=n;++i)scanf("%d",&a[i]),dp[a[i]][i]=i+1;
    10     for(int i=2;i<=58;i++){
    11         for(int j=1;j<=n;j++){
    12             if(!dp[i][j])dp[i][j]=dp[i-1][dp[i-1][j]];
    13             if(dp[i][j])ans=max(ans,i);
    14         }
    15     }printf("%d
    ",ans);
    16     return 0;
    17 }
    View Code

     题目链接

    我们可以先断环为链,然后枚举右端点。相应的,左端点从右端点依次枚举到1.

    那么,设dp[i][j]是区间【i,j】合并的最优策略,则有:

    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]*a[k+1]*a[j+1])

    我们在i与j之间枚举k做断点,后面部分就是新合并后的价值。

    代码:

    #include<cstdio>
    #include<iostream>
    using namespace std;
    struct node{
        int l,r;//l from r to
    }q[300];
    int n,a[300],maxn;
    int dp[300][300];
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]),a[i+n]=a[i];
        for(int j=2;j<=(n<<1);++j){//枚举右端点 
            for(int i=j-1;j-i<n&&i>=1;i--){//向左依次枚举区间 
                for(int k=i;k<j;++k){//枚举断点 
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]*a[k+1]*a[j+1]);
                    maxn=max(dp[i][j],maxn);//每次合并=max(左区间值+右区间值+新合并的值) 
                }//取max 
            }
        }
        printf("%d
    ",maxn);
        return 0;
    }
    View Code
  • 相关阅读:
    朴素贝叶斯分类器实现
    Puppeteer使用
    神经网络常用名词
    Mysql binlog的基本使用和数据库恢复步骤
    webpack之代码分割及页面缓存优化
    webpack之常用loader的配置和使用
    webpack之常用plugin的配置和使用
    第11章 面向对象
    第10章 面向对象
    第9章 模块与包
  • 原文地址:https://www.cnblogs.com/h-lka/p/11146515.html
Copyright © 2020-2023  润新知