• dp学习笔记(各种dp,比较杂)


        HDU1176

        中文题意不多解释了。

        建一个二维dp数组,dp[ i ][ j ]表示第 i 秒落在 j 处一个馅饼。我们需要倒着DP,为什么呢,从 0秒,x=5处出发,假如沿数组正着往下走,终点到哪里我们是不知道的,沿这个路线能最大值,下一秒就未必是最大值。但是我们知道起点,所以我们从终点开始走,走到dp[ 0 ][ 5 ]为止。由于这个总路线存在着诸多未知情况,但是有一点我们可以确定:

            i , j

       i+1,j-1       i+1,j       i+1,j+1

        对于dp[ i ][ j ]这个点,想走到下一步,肯定要加上下一步的更大的值,这个东西是不会变的。所以从最后时间点max-1(其实从max也是可以过的)开始往上走,每次取 i ,j 的下一行三个数的最大值,再加上i,j就可以了。得到方程:

        dp【i】【j】+= max(dp【i+1】【j-1】,dp【i+1】【j】,dp【i+1】【j+1】);

        最后输出dp[0][5]这个起点就可以了。

        

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cstdio>
    using namespace std;
    const int maxn=1e5+10;
    int dp[maxn][12];
    int main()
    {
        int n;
        while(scanf("%d",&n))
        {
            if(n==0)    
                break;
            memset(dp,0,sizeof(dp));
            int t,x;
            int maxx=-1;
            for(int i=1 ;i <= n;i++)
            {
                scanf("%d%d",&x,&t);
                dp[t][x]++;
                if(t>maxx)
                    maxx=t;
            }
            for(int i=maxx-1;i>=0;i--)    
            {
                for(int j=0;j<=10;j++)
                {
                      int mid = max(dp[i+1][j-1],dp[i+1][j]);
                      dp[i][j]+=max(mid,dp[i+1][j+1]);
                }
            }
            printf("%d
    ", dp[0][5]);     
        }
    }

        CodeForces - 455A

         

    Alex doesn't like boredom. That's why whenever he gets bored, he comes up with games. One long winter evening he came up with a game and decided to play it.

    Given a sequence a consisting of n integers. The player can make several steps. In a single step he can choose an element of the sequence (let's denote it ak) and delete it, at that all elements equal to ak + 1and ak - 1 also must be deleted from the sequence. That step brings ak points to the player.

    Alex is a perfectionist, so he decided to get as many points as possible. Help him.

    Input

    The first line contains integer n (1 ≤ n ≤ 105) that shows how many numbers are in Alex's sequence.

    The second line contains n integers a1a2, ..., an (1 ≤ ai ≤ 105).

    Output

    Print a single integer — the maximum number of points that Alex can earn.

    Examples

    Input
    2
    1 2
    Output
    2
    Input
    3
    1 2 3
    Output
    4
    Input
    9
    1 2 1 3 2 2 2 2 3
    Output
    10

    Note

    Consider the third test example. At first step we need to choose any element equal to 2. After that step our sequence looks like this [2, 2, 2, 2]. Then we do 4 steps, on each step we choose any element equals to 2. In total we earn 10 points.

       题意:删除ak,那么这个数组所有的ak+1,ak-1都被删除,同时得到ak分。求能得到的最大分数。

        对每个数进行了桶排序num。这样对后面的差大于1的数没有后效性。。从0位置开始向右消去,那么要消除一个数  i   ,如果i-1被消去,那么i自然也不存在,此时dp[i] = dp[i-1],分数不变,因为i没得删。如果i-1没被删去,那么 dp[ i ] = dp[ i-2 ]+num[i]*i;

        

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+10;
    ll num[maxn];
    ll dp[maxn];
    int main()
    {          
         ll n;
         cin>>n;
         int maxx=-1;
         int x;
         for(int i=1;i<=n;i++)    
         {
             cin>>x;
             num[x]++;
             if(maxx<x)
                 maxx=x;
         }
        dp[1]=num[1];  //初始dp[1]
        for(int i=1 ; i <= maxx;  i++ )
        {
            dp[i]=max(dp[i-1],dp[i-2]+num[i]*i);
        }
        cout<<dp[maxx]<<endl;
    }

      最长递增子序列。

      比如对于{5,6,7,4,2,8,3}  那么最长就是{5,6,7,8}长度为4.

      HDU 1257   最少拦截系统

       中文题意,求最长递增子序列。

      

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn = 3e4+10;
    typedef long long ll;
    ll a[maxn];
    ll dp[maxn];
    int main()
    {
        ll n;
        while(cin>>n)
        {
            
        for(int i=0;i<n;i++)
            cin>>a[i];
        for(int i =0 ; i< n;i++)
            dp[i]=1;
        ll maxx=-1;
        for(int i = 1;i<n;i++)
        {
            for(int j = 0 ; j < i ; j++)
            {
                if(a[i]>a[j])
                {
                    dp[i]=max(dp[i],dp[j]+1);  //还是之前的思想,选或者不选
                }  
            }
            maxx=max(dp[i],maxx);
        }
        cout<<maxx<<endl;}
    }

      求最长递增子序列的和

      HDU1087

      这个样例很误导人啊,这个题意呢,就是从起点跳到终点,保持递增跳而且得分最大。注意,起点和终点是不计分的;

      模板改一下就好了,之前求的是序列长度。这里求和,那么只要把dp初始化a[ ],依然还是探讨每个数选还是不选的问题。转移方程:

      if(a[i]>a[j])
      dp[ i ]=max(dp[ i ],dp[ j ]+a[ i ])

      

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn = 1e6+10;
    typedef long long ll;
    ll a[maxn];
    ll dp[maxn];
    int main()
    {
        ll n ;
        while(cin>>n)
        {
            if(n==0)    
                break;
            for(int i =0  ; i<n;i++)
                cin>>a[i];
            for(int i=0;i<n;i++)
                dp[i]=a[i];
            ll maxx=-1;
            ll sum = 0;
            for(int i = 1 ;i <n ; i++)
            {
                for(int j = 0 ; j< i; j++)
                {
                    if(a[i]>a[j])
                    {
                        dp[i]=max(dp[i],dp[j]+a[i]);
                    }
                }
                maxx=max(dp[i],maxx);
            }
            cout<<maxx<<endl;
        }
    }

       Comet OJ - 模拟赛 #2 Day1

       A 

       我们称长度为 s 的序列  f 为波浪序列,当且仅当满足f1f2>f3...或 f1>f2f3>...且有 s2

       给定一个长度为 n 的序列 {a},输出其有多少个子区间满足其为波浪序列

      

      输入描述

     

      第 11 行包含 11 个正整数,表示序列的长度 nn

      第 22 行包含 nn 个正整数,分别表示序列中的每一个元素 a_iai

      输出描述

     

      输出一行11 个正整数表示答案

      

      5
      1 2 3 2 1      
      输出
      5
      

      样例解释 1

      区间[1,2] [2,3] [3,2] [2,1] [2,3,2] 满足题意,所以共有 5 个波浪序列。

        这个题用DP解比较简单,其他的解法我并没有看懂。所谓波浪,单独一个数字不能算,两个数字一定算上。我们的二维dp,j=0表示下降或相等,j =1 表示上升。i 从0到n

       转移方程:

        

            if(a[i-1]<=a[i])
            {
                dp[i][0]=dp[i-1][1]+1;
            }
            else
                dp[i][1]=dp[i-1][0]+1;
            sum+=dp[i][0]+dp[i][1];

       拿样例来解释一下:

       我们可以写出这么个东西:    0  1

                1       0  1   

                2       0  1  

                3       2  0

                4       1  0

          sum逐个加就是答案。   第一步,从 i =1 开始,出现上升,则dp[1][1]=dp[0][0]+1;

                        第二步,i=2 , 2->3还是上升,但是dp[2][1]不能承接上一个dp[1][1],因为三个上升数不是波浪,需要承接dp[0][1],再加1(差不多算是重置吧)

                       第三步, i =3 ,3->2,出现下降,dp[3][0]=dp[2][1]+1=2,这里说明一下,dp[2][1]存的是2->3,+1里的这个1,是新的3->2,原来的2->3接上3->2,数目还是那个数目,既dp[3][0]=dp[2][1]+1=2。      

          总的来说,这个转移方程中,+1就是加的当前新的波浪,而上一步的dp,是之前的波浪接上了此次的新波浪,数目依然是上一个dp,但是样子已经不同了,所以要用sum进行累加。

        

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long  ll ;
    const int maxn = 3e6+10;
    int dp[maxn][2];
    int a[maxn];
    int main()
    {
        int n ;
        cin>>n;
        for(int i = 0 ; i< n ;i ++)
            cin>>a[i];
        ll sum = 0 ;
        for(int i = 1 ; i< n ; i++)
        {
            if(a[i-1]<=a[i])
            {
                dp[i][0]=dp[i-1][1]+1;
            }
            else
                dp[i][1]=dp[i-1][0]+1;
            sum+=dp[i][0]+dp[i][1];
        }    cout<<sum<<endl;
    }

         区间DP:  

        ACWING石子合并  

        给一堆石子,相邻的合并,问最后得到的最小花费,花费具体算法在题里。

        对于此题,根据常识,对于最终状态,是由两团合并的。所以定义dp[i][j]表示区间i-j合并的最小值。

        二堆合并:dp[1][2]=dp[1][1]+dp[2][2]+sum[1~2];

             即dp[i][i+1]=dp[i][i]+dp[i+1][i+1]+sum[i~i+1];

        三堆合并:dp[1][3]=min(dp[1][1]+dp[2][3],dp[1][2]+dp[3][3])+sum[1~3];

             dp[i][i+2]=min(dp[i][i]+dp[i+1][i+2],dp[i][i+1]+dp[i+2][i+2])+sum[i~i+2];

        综上推广到第i堆到第j堆的合并,dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);

        三个for,相当于遍历每一个长度的区间。

        感觉有点floyed 的意思.....找中转点

        

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    const int maxn = 500;
    const int inf = 1e9;
    int dp[maxn][maxn];
    int a[maxn],sum[maxn];
    using namespace std;
    int main()
    {
        int n;
        cin>>n;
        sum[0]=0;
        for(int i = 1;i <= n ; i++)
        {
            cin>>a[i];
            sum[i]=sum[i-1]+a[i];
        }
        for(int len =2 ; len <= n ;len++)
        {
            for(int l = 1; l+len-1<=n; l++)
            {
                int r=l+len-1;
                dp[l][r]=inf;
                for(int k = l ; k  < r ; k ++)
                {
                    dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1]);
                }
            }
        }
        cout<<dp[1][n]<<endl;
    }

         还是区间DP

         POJ 3280

         题意就是,给定字符串s,长度为m,由n个小写字母构成。在s的任意位置增删字母,把它变为回文串,增删特定字母的花费不同。求最小花费。

         对于样例1:  3  4

                abcb

                a  1000  1100

                b  350  700    c  200  800

              在abcb首端插入bcb花费最小。

  • 相关阅读:
    读《梦断代码》有感
    The Third Group Meeting!
    第一次课程作业项目报告
    第四次读后感
    个人作业进度(五)
    个人作业进度(四)
    个人作业进度(三)
    个人进度(二)
    个人作业进度(一)
    java中类的封装与继承
  • 原文地址:https://www.cnblogs.com/liyexin/p/11862772.html
Copyright © 2020-2023  润新知