[kuangbin带你飞]专题十二 基础DP1 题解+总结

kuangbin带你飞:点击进入新世界

总结:

简单dp,最近在做,持续更新。

1.Max Sum Plus Plus

原题链接:传送门

思路:

  1. 如果不看时间复杂度和空间复杂度的话,假设用2维dp来解决。
  2. dp[i][j]就是前j个数分成i组所取得的最大值,那么有:
  3. dp[i][j]=max(dp[i][j], dp[i][j]+a[j],dp[i-1][k]+a[j] ) k<i
  4. 前两个分别对应不取这个数和取这个数的情况(连续),第三个对应的是在前面取i-1组,在k处断开,然后a[j]为第i组。
  5. 但是1e6的数据范围导致时间和空间都不够,所以需要压缩,可以发现跟背包问题其实是相似的,如果用另外一个数组f[j]来表示dp[i-1][k]的情况,一维就放得下了。
  6. 代码可看注释。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx],dp[manx],f[manx];
int main()
{
    int n,m;
    while(scanf("%d%d",&m,&n)!=EOF) //数据较大,用scanf,不然会超时
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        int maxx;
        memset(dp,0,sizeof(dp));
        memset(f,0,sizeof(f));
        for(int i=1;i<=m;i++)
        {
            maxx=-inf; 
            for(int j=i;j<=n;j++) //分成i组的话那么必须从i开始
            {
                dp[j]=max(dp[j-1]+a[j],f[j-1]+a[j]);
                //f[j-1]是前面j-1个数取i-1组的最大值
                //dp[j-1]是前面j-1个数取i组的最大值
                //那么dp[j]就是j个数分i组的最大值
                f[j-1]=maxx; 
   //这里j-1是因为maxx是上个循环得到的,就是前面dp[j-1]和maxx的较大值
                maxx=max(dp[j],maxx);
        //假设当前是循环到i组的时候,这个f数组是会在i+1组的时候用到
            }
        }
        printf("%d\n",maxx);
    }
    return 0;
}



2.Ignatius and the Princess IV

原题链接:传送门

思路:

  1. 跟dp好像没什么关系,用一个map计数即可。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx];
map<int,int>mp;
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        mp.clear();
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            mp[a[i]]++;
        }
         for(int i=1;i<=n;i++)
            if(mp[a[i]]>=(n+1)/2)
            {
                printf("%d\n",a[i]);
                break;
            }
    }
    return 0;
}


3.Monkey and Banana

原题链接:传送门

思路:

  1. 求最大子序列和,不过附加了一些其他条件:
  2. 后续的长和宽必须小于前面的
  3. 这题主要是输入的时候把六种情况都存进去就可以了。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1000;
int dp[manx];
struct node{
    int l,r,w;
}a[manx];
bool cmp(node a,node b){
    if(a.l==b.l) return a.r<b.r;
    return a.l<b.l;
}
int main()
{
    int n,t=1;
    while(scanf("%d",&n)!=EOF)
    {
        if(!n) break;
        int index=1;
        for(int i=1;i<=n;i++)
        {
            int l,r,w;
            scanf("%d%d%d",&l,&r,&w);
            a[index].l=l,a[index].r=r,a[index++].w=w;
            a[index].l=l,a[index].r=w,a[index++].w=r;
            a[index].l=r,a[index].r=l,a[index++].w=w;
            a[index].l=r,a[index].r=w,a[index++].w=l;
            a[index].l=w,a[index].r=l,a[index++].w=r;
            a[index].l=w,a[index].r=r,a[index++].w=l;

        }
        sort(a+1,a+1+index,cmp);
        memset(dp,0,sizeof(dp));
        int ans=0;
        for(int i=1;i<index-1;i++){
            for(int j=i;j<index;j++)
                if(a[i].l<a[j].l&&a[i].r<a[j].r)
                    dp[j]=max(dp[j],dp[i]+a[j].w),ans=max(ans,dp[j]);
        }
        cout<<"Case "<<t++<<": maximum height = "<<ans<<endl;
    }
    return 0;
}


4.最少拦截系统

原题链接:传送门

思路:

  1. LIS模板题,很经典的DP。
  2. dp[i]表示前i个至少需要的导弹数。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx],dp[manx];
int main()
{
    int n,t=1;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),dp[i]=1;
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
                if(a[j]>a[i])
                    dp[j]=max(dp[i]+1,dp[j]);
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}


5.Longest Ordered Subsequence

原题链接:传送门

思路:

  1. 最长上升子序列,跟上面那一题相同。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e6+5;
int a[manx],dp[manx];
int main()
{
    int n,t=1;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),dp[i]=1;
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
                if(a[j]>a[i])
                    dp[j]=max(dp[i]+1,dp[j]);
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}


6.Common Subsequence

原题链接:传送门

思路:

  1. 最长相同子序列,dp[i][j]是第一个串的前i个和第二个串的前j个最大相同序列长度数。
  2. 由第一个开始递推,dp[n][m]就是答案。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e3+5;
string s1,s2;
int dp[manx][manx];
int main()
{
    int n,t=1;
    while(scanf("%s%s",&s1,&s2)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=s1.size();i++)
            for(int j=1;j<=s2.size();j++)
                if(s1[i-1]==s2[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        cout<<dp[s1.size()][s2.size()]<<endl;
    }
    return 0;
}


7.Super Jumping! Jumping! Jumping!

原题链接:传送门

思路:

  1. 最大递增子序列的序列和。
  2. dp[i]为前i个数中的最大值。
  3. 把dp[i]初始化为a[i]
  4. 那么dp[i]=max(dp[i],dp[k]+a[k]) when k<i&&a[k]<a[i]

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e3+5;
int dp[manx];
int a[manx];
int main()
{
    int n,t=1;
    while(scanf("%d",&n)!=EOF)
    {
        if(!n) break;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),dp[i]=a[i];
        int ans=0;
        for(int i=1;i<=n;i++){
            for(int j=i+1;j<=n;j++)
                if(a[j]>a[i])
                    dp[j]=max(dp[j],dp[i]+a[j]);
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}


8.Milking Time

原题链接:传送门

思路:

  1. dp[i]日常表示前i个数的最大价值。
  2. 这题主要是把数据处理一下,把结束时间加上r,然后记得把结构体排序。
  3. 那么就转化成一维的最大子序列和了。
  4. dp[i]=max(dp[i],dp[k]+a[j].w) when a[k].r<=a[i].l&&l>k

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e3+5;
int dp[manx];
struct node{
    int l,r,w;
}a[manx];
bool cmp(node a,node b){
    return a.l<b.l;
}
int main()
{
    int n,m,r,t=1;
    while(scanf("%d%d%d",&n,&m,&r)!=EOF)
    {
        for(int i=1;i<=m;i++){
            cin>>a[i].l>>a[i].r>>a[i].w;
            a[i].r+=r;
        }
        sort(a+1,a+1+m,cmp);
        int ans=0;
        for(int i=1;i<=m;i++)
            dp[i]=a[i].w;
        for(int i=1;i<=m;i++){
            for(int j=i+1;j<=m;j++)
                if(a[j].l>=a[i].r)
                    dp[j]=max(dp[j],dp[i]+a[j].w);
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}



9.FatMouse and Cheese

原题链接:传送门

思路:

  1. 这道题的话,跟地图相关,搜索是必须的。
  2. 放在dp专题里面,无非就是记忆化搜索,注意最后return 。
  3. dp[x][y]=a[x][y]+mx
  4. mx为这个点开始往后搜索得到的最大值。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int dp[manx][manx],a[manx][manx];
int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
int n,m,r,t=1;
int dfs(int x,int y){
    if(dp[x][y]) return dp[x][y];
    int mx=0;
    for(int i=1;i<=m;i++){
        for(int j=0;j<4;j++){
            int xx=x+dx[j]*i,yy=y+dy[j]*i;
            if(a[x][y]<a[xx][yy]&&xx>0&&yy>0&&xx<=n&&yy<=n)
                mx=max(dfs(xx,yy),mx);
        }
    }
    return dp[x][y]=a[x][y]+mx;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==-1&&m==-1) break;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                cin>>a[i][j];
        memset(dp,0,sizeof(dp));
        cout<< dfs(1,1)<<endl;
    }
    return 0;
}


10.Phalanx

原题链接:传送门

思路:

  1. 最大对称子矩阵,左下角到右上角为对称轴。
  2. 枚举每一个点,如果相同,把行数x向上扩展,列数y相右扩展。
  3. dp[i][j]=min(dp[i-1][j+1],i-x-1)+1 when c[i][j+1]==c[i-1][j]

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int dp[manx][manx];
char c[manx][manx];
int n,m,r,t=1;
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(!n) break;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                cin>>c[i][j];
        memset(dp,0,sizeof(dp));
        int ans=1;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++){
                dp[i][j]=1;
                int x=i,y=j;
                while(c[i][y]==c[x][j]) x--,y++;
                if(c[i][j+1]==c[i-1][j])
                    dp[i][j]=min(dp[i-1][j+1],i-x-1)+1;
                ans=max(ans,dp[i][j]);
            }
        cout<<ans<<endl;
    }
    return 0;
}



11.FatMouse’s Speed

原题链接:传送门

思路:

  1. 输入保存一下数据,然后排个序,就是简单的最长xx子序列。
  2. 因为后面需要输出路径,所以在枚举的时候可以逆向枚举i,再从后面往前面更新,这样输出的时候就可以根据ans的值来进行输出。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int dp[manx],q[manx];
struct node {
    int w,v,x;
}a[manx];
bool cmp(node a,node b){
    if(a.w==b.w) return a.v>b.v;
    return a.w<b.w;
}
int n,m,r,t=0;
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        a[++t].w=n,a[t].v=m,a[t].x=t;
    }
    sort(a+1,a+t+1,cmp);
    int ans=0;
    for(int i=t;i>=1;i--){
        dp[i]=1;
        for(int j=i+1;j<=t;j++)
            if(a[j].w>a[i].w&&a[j].v<a[i].v)
                dp[i]=max(dp[j]+1,dp[i]);
        ans=max(dp[i],ans);
    }
    cout<<ans<<endl;
    for(int i=1;i<=t;i++){
        if(dp[i]==ans)
            cout<<a[i].x<<endl,ans--;
    }
    return 0;
}


12.免费馅饼

原题链接:传送门

思路:

  1. 如果把时间轴看成行数,每个点在这个时间上获得的饼数看成列数,那么a[i][j]表示在时间为i时j点获得的饼,如果把图画出来就可以发现这其实是一道数塔题。
  2. 因为要处理相邻两边,下标为0的时候不好处理,所以把位置+1;
  3. 既然是数塔题,那么显而易见:
  4. dp[i][j]=max( dp[i+1][j-1] , dp[i+1][j] , dp[i+1][j+1] )

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=1e5+5;
int dp[manx][15];
int a[manx][15];
int n,m,r,t=0;
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(!n) break;
        memset(dp,0,sizeof(dp));
        memset(a,0,sizeof(a));
        int e=0;
        for(int i=1;i<=n;i++){
            int x,t;
            scanf("%d%d",&x,&t);
            a[t][++x]++;
            e=max(e,t);
        }
        for(int i=e;i>=0;i--)
            for(int j=1;j<=11;j++)
                dp[i][j]=max(dp[i+1][j-1],
                    max(dp[i+1][j],dp[i+1][j+1]))+
                    a[i][j];
        printf("%d\n",dp[0][6]);
    }
    return 0;
}


13.Tickets

原题链接:传送门

思路:

  1. 只能两个人一起买票,那么递推式很容易推出来:
  2. dp[i]=min(dp[i-1]+a[i] , dp[j-2]+b[i])
  3. 此处为单独买,和前一个人的买的较大值
  4. dp[n]为答案 。

    代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll unsigned long long
#define inf 1<<30
using namespace std;
const int manx=2e3+5;
int a[manx],b[manx],dp[manx];
int n,m,r,t=0;
int main()
{
    while(scanf("%d",&t)!=EOF)
    {
        while(t--){
            cin>>n;
            for(int i=1;i<=n;i++) cin>>a[i];
            for(int i=2;i<=n;i++) cin>>b[i];
            dp[1]=a[1];
            for(int i=2;i<=n;i++)
                dp[i]=min(dp[i-1]+a[i],dp[i-2]+b[i]);
            int h=8,mi=0,sec=0;
            sec+=dp[n];
            mi+=sec/60%60;
            h+=sec/3600;
            sec%=60;
            printf("%02d:%02d:%02d ",h,mi,sec);
            if(h>=12) printf("pm\n");
            else printf("am\n");
        }
    }
    return 0;
}


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值