1. 程式人生 > >「kuangbin帶你飛」專題十二 基礎DP

「kuangbin帶你飛」專題十二 基礎DP

bank 結束 cut maximum 狀態 you else if tao 全部


layout: post
title: 「kuangbin帶你飛」專題十二 基礎DP
author: "luowentaoaa"
catalog: true
tags:
mathjax: true
- kuangbin
- 動態規劃


傳送門

A.HDU1024 Max Sum Plus Plus

題意

給你N個數,然後你分成M個不重疊部分,並且這M個不重疊部分的和最大.

思路

動態規劃最大m字段和,dp數組,dp[i][j]表示以a[j]結尾的,i個字段的最大和

兩種情況:1.第a[j]元素單獨作為第i個字段
        2.第a[j]元素和前面的字段共同當做第i個字段

得到狀態轉移方程:dp[i][j]=max( dp[i][j-1]+a[j] , max(dp[i-1][t])+a[j]);

但是實際情況是,時間復雜度和空間復雜度都是相當的高,所以要進行時間和空間的優化:
    將每次遍歷的時候的max(dp[i-1][t]) 用一個數組d儲存起來,這樣就能省去尋找max(dp[i-1][t])的時間,
    這樣狀態轉移方程就變成了 dp[i][j]=max( dp[i][j-1]+a[j] , d[j-1]+a[j]), 會發現dp數組的可以
    省去一維,因為每次都是和前一次的狀態有關,所以可以記錄前一次狀態,再用一個變量tmp記錄下dp[i][j-1],
    這樣方程就變成了 dp[j]=max( num+a[j] , d[j-1]+a[j]);這樣就可以化簡一下就是:
    dp[j]= max( num , d[j-1])+a[j];
    在之後還要保存前面m-1的情況的最大狀態,d[j-1]=ma;等於只有M-1組而且到這個位置時候的值,註意不能最大,因為最大雖然值大但是不保證是M-1個
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int a[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int m,n;
    while(cin>>m>>n){
        for(int i=1;i<=n;i++)cin>>a[i];
        int ma=0;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=m;i++){
            int num=0;
            for(int j=1;j<=i;j++)num+=a[j];
            ma=num;
            for(int j=i+1;j<=n;j++){
                num=max(num,dp[j-1])+a[j];//前面的和,自己組成一個
                dp[j-1]=ma;
                ma=max(ma,num);
            }
        }
        cout<<ma<<endl;
    }
    return 0;
}

B.HDU1029 Ignatius and the Princess IV

題意

給你n個數字,你需要找出出現至少(n+1)/2次的數字 現在需要你找出這個數字是多少?

方法一

直接用map記錄次數然後掃一遍

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
map<int,int>mp;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        int a;
        mp.clear();
        for(int i=0;i<n;i++){
            cin>>a;
            mp[a]++;
        }
        map<int,int>::iterator it;
        int maid,ma=0;
        for(it=mp.begin();it!=mp.end();it++){
            if(it->second>=(n+1)/2){
                maid=it->first;
            }
        }
        cout<<maid<<endl;
    }
    return 0;
}

方法二

摩爾投票法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        int k,sum=0,a;
        while(n--){
            cin>>a;
            if(!sum)k=a,sum++;
            else if(k==a)sum++;
            else{
                sum--;
            }
        }
        cout<<k<<endl;
    }
    return 0;
}

方法三

排序後輸出中間位置的數

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int a[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        for(int i=0;i<n;i++)
            cin>>a[i];
        sort(a,a+n);
        cout<<a[(n+1)/2]<<endl;
    }
    return 0;
}

C.HDU1069 Monkey and Banana

題意

三維的最長帶全嚴格上升子序列N個無限個數的三維方體,三維方體可以任意旋轉(長寬高可以替換),現在讓你求出這些正方體可以搭的最高高度,要求上面的矩形的長和寬必須嚴格小於下面的矩形的長寬,類似與三維的最長帶全嚴格上升子序列

思路

一對長寬高可以構造出六種不同的正方體(長:三個取一個,寬:兩個取一個,高:被固定了);然後根據長來排序,長相同根據寬排序,高不影響不管,依次判斷最小面的是哪一個矩形,然後DP出這個矩形在下面時上面放那個矩形的最大值,最後求出一個最大值就行

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
struct node{
    int x,y,z;
    node(int x,int y,int z):x(x),y(y),z(z){};
};
int cmp(node a,node b){
    if(a.x==b.x)return a.y<b.y;
    else return a.x<b.x;
}
vector<node>ve;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    int t=1;
    while(cin>>n&&n){
        ve.clear();
        for(int i=0;i<n;i++){
            int a,b,c;
            cin>>a>>b>>c;
            ve.push_back(node(a,b,c));ve.push_back(node(a,c,b));
            ve.push_back(node(b,a,c));ve.push_back(node(b,c,a));
            ve.push_back(node(c,a,b));ve.push_back(node(c,b,a));
        }
        int len=ve.size();
        sort(ve.begin(),ve.end(),cmp);
        int sum=0;
        for(int i=0;i<len;i++){
            int ma=0;
           // cout<<"i="<<i<<" ";
           // cout<<ve[i].x<<" "<<ve[i].y<<" "<<ve[i].z<<endl;
            for(int j=0;j<i;j++){
                if(ve[i].x>ve[j].x&&ve[i].y>ve[j].y&&ve[j].z>ma){
                    ma=ve[j].z;
                }
            }
            ve[i].z=ma+ve[i].z;
            sum=max(sum,ve[i].z);
        }
        cout<<"Case "<<t++<<": maximum height = "<<sum<<endl;
    }
    return 0;
}

D.HDU1074 Doing Homework

題意

有n個任務,每個任務有一個截止時間,超過截止時間一天,要扣一個分。
求如何安排任務,使得扣的分數最少,多種可能輸出字典序最小的

思路

狀壓DP! 觀察到N最大只有15,跟南京網絡賽的E一樣思路,想到了暴力枚舉每個狀態;然後枚舉每一個在這狀態完成的任務,假設他沒完成讓他在只有這個任務沒完成的情況下完成的情況,因為題目要求是字典序小的先輸出,所以同樣的情況先枚舉字典序大的,如果枚舉完大的後,發現小的更好,那就可以直接替換了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int last[16];
int need[16];
int pre[maxn];
int used[maxn];
string s[16];
void init(){
    memset(dp,inf,sizeof(dp));
    memset(used,0,sizeof(used));
}
void out(int x){
    if(!x)return;
    out(x^(1<<pre[x]));
    cout<<s[pre[x]]<<endl;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        init();
        for(int i=0;i<n;i++){
            cin>>s[i]>>last[i]>>need[i];
        }
        dp[0]=0;
        for(int i=1;i<(1<<n);i++){
            for(int j=n-1;j>=0;j--){
                if(!(i&(1<<j)))continue;
                int pp=i^(1<<j);
                int time=used[pp]+need[j]-last[j];
                if(time<0)time=0;
                if(dp[i]>dp[pp]+time){
                    dp[i]=dp[pp]+time;
                    used[i]=used[pp]+need[j];
                    pre[i]=j;
                }
            }
        }
        cout<<dp[(1<<n)-1]<<endl;
        out((1<<n)-1);
    }
    return 0;
}

E.HDU1087 Super Jumping! Jumping! Jumping!

題意

求最大遞增子序列的權值和

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int a[1100];
ll dp[1100];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n&&n){
        for(int i=0;i<n;i++)cin>>a[i],dp[i]=a[i];
        ll sum=0;
        ll ma=0;
        for(int i=0;i<n;i++){
            ll num=0;
            for(int j=0;j<i;j++){
                if(a[i]>a[j]&&num<dp[j]){
                    num=dp[j];
                }
            }
            dp[i]=a[i]+num;
            ma=max(dp[i],ma);
        }
        cout<<ma<<endl;
    }
    return 0;
}

F.HDU1114 Piggy-Bank

題意

有一個存錢罐,給出它的重量和裝滿硬幣的重量,然後給出裏面裝的硬幣的種類數,並給出每種硬幣的面值和重量,求在給定重量的條件下硬幣的最小價值

思路

根據重量從0開始推到給定的重量,每個從這個重量之前的重量找一個硬幣的差距,然後選出一個最小值;默認為inf

完全背包:必須裝滿給出的重量,因此要使dp[0]=0,同時因為求的是最小值,因此其他位置應該是正無窮。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[maxn];
int p[maxn],w[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int e,f;
        cin>>e>>f;
        int num=f-e;
        int n;
        cin>>n;
        memset(dp,inf,sizeof(dp));
        for(int i=0;i<n;i++){
            cin>>p[i]>>w[i];
        }
        for(int i=0;i<=num;i++){
            for(int j=0;j<n;j++){
                if(w[j]>i){/*cout<<"one   "<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;*/continue;}
                else if(dp[i-w[j]]==inf&&w[j]!=i){/*cout<<"two   "<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;*/continue;}
                else{
                    int k=dp[i-w[j]]==inf?p[j]:dp[i-w[j]]+p[j];
                    dp[i]=min(dp[i],k);
                   // cout<<"三   "<<dp[i]<<"i=="<<i<<" j="<<j<<"w[j]="<<w[j]<<endl;
                }
            }
            //cout<<"dp["<<i<<"]="<<dp[i]<<endl;
        }
        if(dp[num]==inf)cout<<"This is impossible."<<endl;
        else cout<<"The minimum amount of money in the piggy-bank is "<<dp[num]<<"."<<endl;
    }
    return 0;
}
//完全背包的代碼;
#include <iostream>
using namespace std;
#define INF 2000000
int main()
{
    int t;
    int n;
    int w,wa,wb;
    int value[505],weight[505];
    int dp[10005];
    while(cin>>t)
    {
        while(t--)
        {
            cin>>wa>>wb;
            w = wb-wa;
            cin>>n;
            for(int i=0;i<n;i++)
            {
                cin>>value[i]>>weight[i];
            }
            for(int i=0;i<=w;i++)dp[i] = INF;
            dp[0] = 0;
            for(int i=0;i<n;i++)
            {
                for(int j=weight[i];j<=w;j++)
                {
                    if(dp[j]>(dp[j-weight[i]]+value[i]))dp[j] = dp[j-weight[i]]+value[i];
                }
            }
            if(dp[w]>=INF)cout<<"This is impossible."<<endl;
            else{
                cout<<"The minimum amount of money in the piggy-bank is ";
                cout<<dp[w]<<"."<<endl;
            }
        }
    }
    return 0;
}

G.HDU1176 免費餡餅

題意

中文題

思路

二維DP,反向DP,
\[ DP[i][j]=max(dp[i+1][j],dp[i+1][j-1],dp[i+1][j+1])+dp[i][j] \]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
ll dp[maxn][12];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n&&n){
        memset(dp,0,sizeof(dp));
        int m=0;
        for(int i=0;i<n;i++){
            int a,b;
            cin>>a>>b;
            dp[b][a]++;
            if(m<b)m=b;
        }
        for(int i=m-1;i>=0;i--){
            for(int j=0;j<=10;j++){
                if(j==0){
                    dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
                }
                else if(j==10){
                    dp[i][j]+=max(dp[i+1][j],dp[i+1][j-1]);
                }
                else{
                    dp[i][j]+=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]));
                }
            }
        }
        cout<<dp[0][5]<<endl;
    }
    return 0;
}

H.HDU1260 Tickets

題意

現在有n個人要買電影票,如果知道每個人單獨買票花費的時間,還有和前一個人一起買花費的時間,問最少花多長時間可以全部買完票。

思路

關註最後一個買票的人,如果他自己買,那就是選擇前面那個人的(和前面的前面那個買還是自己買的)的最小值;如果他和前面的買,那就減去前面自己買的錢,加上一起買的錢,最後選出最小值即可,這裏因為考慮的只關於前一個人所以可以開一個二維滾動數組;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int a[2005],b[2005];
int dp[2][3];
int main()
{
  //  std::ios::sync_with_stdio(false);
  //  std::cin.tie(0);
   // std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        for(int i=2;i<=n;i++)cin>>b[i];
        dp[1][1]=inf;dp[1][0]=a[1];//0是自己買,1是跟前面的買
        for(int i=2;i<=n;i++){
            dp[i%2][0]=min(dp[(i+1)%2][0],dp[(i+1)%2][1])+a[i];
            dp[i%2][1]=dp[(i+1)%2][0]+b[i]-a[i-1];
        }
        int time=min(dp[n%2][0],dp[n%2][1]);
        int h=8,m=0,s=0;
        h+=time/3600;time%=3600;m=time/60;time%=60;s=time;
        h%=24;
        int flag=0;
        if(h<=12)flag=0;
        else if(h>12){
            flag=1;
        }
        printf("%02d:%02d:%02d %s\n",h,m,s,flag?"pm":"am");
    }
    return 0;
}

I.HDU1257 最少攔截系統

思路

本質上是求最長嚴格上升子序列,模擬成題意,就是

1.第一顆炮彈塞進數組中 攔截系統+1;

2.第二個炮彈大於第一顆炮彈,那就只能攔截系統+1;但是這個炮彈還能打比它小的導彈所以塞進數組中

3.如果第三個炮彈小於等於前面已經射出去的最高的炮彈,那就說明它可以被前面用過的炮彈打中,但是最優的策略是在前面用過的炮彈中選出一個大於它最小的炮彈用來替換,說明這個炮彈現在的位置在這了,如果等於最好,(但是有點題目是不能等於,比如最大不嚴格上升子序列,那就只能用upper_bound)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
vector<int>ve;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        ve.clear();
        int len=0;
        for(int i=0;i<n;i++){
            int a;
            cin>>a;
            if(len==0||ve[len-1]<a){//如果不嚴格的話把小於搞成<=a 說明這個炮彈等於它高度的它也打不中,只能換新炮彈了
                ve.push_back(a);
                len++;
            }
            else{
                int j=lower_bound(ve.begin(),ve.end(),a)-ve.begin();//找出最小最接近的用過的炮彈,數組是遞增的。如果是不嚴格就改成upper
                ve[j]=a;
            }
        }
        cout<<len<<endl;

    }
    return 0;
}

J.HDU1160 FatMouse‘s Speed

題意

找到一個最多的老鼠序列,使得序列中的老鼠的體重滿足遞增,相應老鼠的速度滿足遞減。

思路

先按體重遞增進行sort排序,然後按照體重找到最長遞減子序列即可,用動態規劃做比較簡單。狀態f[i]表示前i個老鼠中的最長遞減子序列長度,狀態轉移方程為f[i] = max{f[j], mice[j].speed > mice[i].speed} + 1, 最後找出最大的f[i]即可。註意此題還需要輸出找到的序列中的老鼠的最原始的標號,因此不僅要在剛開始的時候把每個老鼠的最初的序號記下來,還要在進行狀態轉移的時候把當前的老鼠的位置標記下來。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
struct node{
    int w,s,id;
}my[1005];
int cmp(node a,node b){
    if(a.w==b.w)return a.s>b.s;
    else return a.w<b.w;
}
int dp[1005];
int pre[1005];
void out(int x){
    if(!x)return;
    out(pre[x]);
    cout<<my[x].id<<endl;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n=1;
    while(cin>>my[n].w>>my[n].s){
        my[n].id=n;
        n++;
    }
    n=n-1;
    for(int i=1;i<=n;i++)dp[i]=1;
    sort(my+1,my+n+1,cmp);
    int maid,malen=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            if(my[j].w<my[i].w&&my[j].s>my[i].s&&dp[i]<dp[j]+1){
                dp[i]=dp[j]+1;
                pre[i]=j;
                if(dp[i]>malen)maid=i,malen=dp[i];
            }
        }
    }
    cout<<malen<<endl;
    out(maid);
    return 0;
}

K.POJ1015 Jury Compromise

題意

在遙遠的國家佛羅布尼亞,嫌犯是否有罪,須由陪審團決定。陪審團是由法官從公眾中挑選的。先隨機挑選n 個人作為陪審團的候選人,然後再從這n 個人中選m 人組成陪審團。選m 人的辦法是:控方和辯方會根據對候選人的喜歡程度,給所有候選人打分,分值從0 到20。為了公平起見,法官選出陪審團的原則是:選出的m 個人,必須滿足辯方總分D和控方總分P的差的絕對值|D-P|最小。如果有多種選擇方案的|D-P| 值相同,那麽選辯控雙方總分之和D+P最大的方案即可。

輸出:

選取符合條件的最優m個候選人後,要求輸出這m個人的辯方總值D和控方總值P,並升序輸出他們的編號。
#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int dp[21][801];
vector<int> path[21][801];
 
int main()
{
    int times=1;
    int subtraction[201],_plus[201];
    int n,m,i,j,k;
    while(~scanf("%d%d",&n,&m) && n && m)
    {
        for(i=0;i<m;++i)
            for(j=0;j<801;++j)
                path[i][j].clear();
        memset(dp,-1,sizeof(dp));
        int d,p;
        for(i = 0; i < n; i++)
        {
            cin>>d>>p;
            subtraction[i] = d-p;
            _plus[i] = d+p;
        }
        int fix = 20*m;
        dp[0][fix] = 0;
        for(k = 0; k < n; k++)
            for(i = m-1; i >= 0; i--)
            {
                for(j = 0; j < 2*fix; j++)
                {
                    if(dp[i][j] >= 0)
                    {
                        if(dp[i+1][j+subtraction[k]] <= dp[i][j] + _plus[k])
                        {
                            dp[i+1][j+subtraction[k]] = dp[i][j] + _plus[k];
                            path[i+1][j+subtraction[k]] = path[i][j];
                            path[i+1][j+subtraction[k]].push_back(k);
                        }
                    }
                }
            }
        for(i = 0; dp[m][fix+i] == -1 && dp[m][fix-i] == -1; i++);
        int temp = (dp[m][fix+i] > dp[m][fix-i]) ? i : -i;
        int sumD = ( dp[m][fix+temp] + temp )/2;
        int sumP = ( dp[m][fix+temp] - temp )/2;
        printf( "Jury #%d\n", times++ );
        printf( "Best jury has value %d for prosecution and value %d for defence:\n", sumD,sumP);
        for( i=0; i < m; i++ )
            printf( " %d", path[m][fix+temp][i]+1);
        printf( "\n\n" );
 
    }
    return 0;
}

L.POJ1458 Common Subsequence

題意

最長公共子序列

思路

\[ if(s[i]==s[j])dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=max(d[i-1][j],dp[i][j-1]); \]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e3+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int dp[maxn][maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    char a[1500],b[1500];
    while(cin>>a+1>>b+1){
        int n,m;
        n=strlen(a+1);m=strlen(b+1);
        int k=max(n,m);
        memset(dp,0,sizeof(dp));
        int ma=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(a[i]==b[j]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else{
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        cout<<dp[n][m]<<endl;
    }
    return 0;
}

M.POJ1661 Help Jimmy

題意

中文題,小人從最高點跳下

1.可以跳到板子上
2.可以直接跳到地上
3.下落的距離不能超過MAX
4.可以在板子上左右移動,但是要花費時間;
5.問從起點落到地面的最小值

思路

1.可以從下枚舉上去到起點
2.對於每一個板子,都可以落到它下面的符合條件的板子(可以正好接住)
3.對於每一個板子,都可以從左邊或者右邊落下,分別枚舉左右邊即可
4.假設DP(i-0)表示這個板子從左邊落到地面花費的時間
DP(i-1)表示這個板子從右邊左到地面花費的時間
一直枚舉到最上面的起點即可,復雜度O(n^2)

\[ DP[i][0]=max(DP[j][0]+走到左邊花費的時間,DP[j][1]+走到右邊花費的時間)+從i落到J花費的時間 \]

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int x,y,MAX,n;
struct node{
    int l,r;
    int h;
}my[maxn];
int cmp(node a,node b){
    return a.h<b.h;
}
int dp[maxn][2];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        cin>>n>>x>>y>>MAX;
        my[0].l=-inf;my[0].r=inf;my[0].h=0;
        for(int i=1;i<=n;i++)cin>>my[i].l>>my[i].r>>my[i].h;
        my[n+1].l=x;my[n+1].r=x;my[n+1].h=y;
        sort(my,my+n+1,cmp);
        memset(dp,inf,sizeof(dp));
        dp[1][0]=dp[1][1]=my[1].h;
        dp[0][0]=dp[0][1]=0;
        for(int i=1;i<=n+1;i++){
            for(int j=0;j<i;j++){
                if(my[j].l<=my[i].l&&my[j].r>=my[i].l&&my[i].h-my[j].h<=MAX){
                    if(j==0){
                        dp[i][0]=my[i].h;
                    }
                    else{
                        dp[i][0]=min(dp[j][0]+my[i].l-my[j].l,dp[j][1]+my[j].r-my[i].l)+my[i].h-my[j].h;
                    }
                }
                if(my[j].l<=my[i].r&&my[j].r>=my[i].r&&my[i].h-my[j].h<=MAX){
                    if(j==0){
                        dp[i][1]=my[i].h;
                    }
                    else{
                        dp[i][1]=min(dp[j][0]+my[i].r-my[j].l,dp[j][1]+my[j].r-my[i].r)+my[i].h-my[j].h;
                    }
                }
            }
        }
        cout<<min(dp[n+1][0],dp[n+1][1])<<endl;
    }
    return 0;
}

N.POJ2533 Longest Ordered Subsequence

題意

最長遞增子序列

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
vector<int>ve;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        vector<int>ve;
        int len=0;
        for(int i=0;i<n;i++){
            int a;
            cin>>a;
            if(!len||a>ve[len-1]){
                ve.push_back(a);
                len++;
            }
            else{
                ve[lower_bound(ve.begin(),ve.end(),a)-ve.begin()]=a;
            }
        }
        cout<<len<<endl;
    }
    return 0;
}

O.POJ3186 Treats for the Cows

題意

給出n個數字v(i),每次你可以取出最左邊的數字或者取出最右邊的數字,一共取n次取完。假設你第i次取的數字是x,那麽你可以獲得i*x的價值。現在你需要規劃取數順序,使得總價值和最大。

思路

區間DP,方程
\[ dp[i][j] = max(dp[i + 1][j] + v[i]*(n-j+i),dp[i][j-1]+v[j]*(n-j+i)) \]

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[2001][2001];
int v[2001];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    while(cin>>n){
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++){
            cin>>v[i];dp[i][i]=v[i];
        }
        for(int i=n;i>=1;i--){
            for(int j=i;j<=n;j++){
                dp[i][j]=max(dp[i+1][j]+v[i]*(n-j+i),dp[i][j-1]+v[j]*(n-j+i));
            }
        }
        cout<<dp[1][n]<<endl;
    }
    return 0;
}

P.HDU1078 FatMouse and Cheese

題意

有一種遊戲是的玩法是這樣的:
有一個n*n的格子,每個格子有一個數字。
遵循以下規則:
1. 玩家每次可以由所在格子向上下左右四個方向進行直線移動,每次移動的距離不得超過m
2. 玩家一開始在第一行第一列,並且已經獲得該格子的分值
3. 玩家獲得每一次移動到的格子的分值
4. 玩家下一次移動到達的格子的分值要比當前玩家所在的格子的分值要大。
5. 遊戲所有數字加起來也不大,保證所有數字的和不會超過int型整數的範圍
6. 玩家僅能在n*n的格子內移動,超出格子邊界屬於非法操作
7. 當玩家不能再次移動時,遊戲結束
現在問你,玩家所能獲得的最大得分是多少?

思路

記憶化搜索,把每次搜索的結果存在DP裏面 dp數組表示,這一個點接下去搜索能搜索到的最大值

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int n,m;
int mp[110][110];
int dp[110][110];
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int dfs(int x,int y){
    if(dp[x][y]!=-1)return dp[x][y];///說明這個點已經被用過了;
    else{
        int ans=0;
        for(int i=1;i<=m;i++){///步數限制
            for(int j=0;j<4;j++){///方向限制
                int xx=x+dx[j]*i;
                int yy=y+dy[j]*i;
                if(xx>=1&&xx<=n&&yy>=1&&yy<=n){
                    if(mp[xx][yy]>mp[x][y])
                        ans=max(ans,dfs(xx,yy));
                }
            }
        }
        dp[x][y]=mp[x][y]+ans;
    }
    return dp[x][y];
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n>>m&&!(n==-1&&m==-1)){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++)
                cin>>mp[i][j];
        }
        memset(dp,-1,sizeof(dp));
        dfs(1,1);
        cout<<dp[1][1]<<endl;
    }
    return 0;
}

Q.HDU2859 Phalanx

題意

給你一個矩陣,只由小寫或大寫字母構成。求出它的最大對稱子矩陣的邊長。

其中對稱矩陣是一個kk的矩陣,它的元素關於從左下角到右上角的對角線對稱。
例如下面這個3
3的矩陣是對稱矩陣:
cbx
cpb
zcc

思路

dp ij 表示以點i,j為左下角的最大對稱矩陣;發現以下規律

1.對於矩陣的最上面和最右邊 i=1或者j=n的點為左下角的對稱矩陣最大為1(就是它自己)

2.然後根據1可以得知,第二行最大的矩陣大小為2;並且最大的矩陣比第一個矩陣要多一個左邊和右邊的大小;

3.得出對於一個以該點為左下角的對稱矩陣的最大大小由它的右上角的對稱矩陣推出,而且最多比它大一圈;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1<<16;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int dp[1100][1100];
int n;
char s[1100][1100];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n&&n){
        for(int i=1;i<=n;i++)cin>>s[i]+1;
        int ma=1;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)dp[1][i]=dp[i][n]=1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==1||j==n)continue;
                int xx=i,yy=j,num=0;
                while(xx>=1&&yy<=n&&s[xx][j]==s[i][yy]){
                    num++;
                    xx--;yy++;
                }
                int pre=dp[i-1][j+1];
                if(num>=pre+1)dp[i][j]=pre+1;
                else dp[i][j]=num;
                ma=max(ma,dp[i][j]);
              //  cout<<"i="<< i<<" "<<"j="<<j<<" "<<ma<<" "<<dp[i][j]<<endl;
            }
        }
        cout<<ma<<endl;
    }
    return 0;
}

R.POJ3616 Milking Time

題意

M個時間段,每個時間段有V價值,在每個時間段之間必須隔著R的時間,求最大價值和

思路

把時間段的起點排序一下,然後對於每個時間段判斷前面的時間段間隔滿不滿足題意。然後選出價值最大的

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1200;
//const ll inf=0x3f3f3f3f3f3f3f3fLL;
const int inf=0x3f3f3f3f;
int n,m,R;
struct node{
    int l,r;
    int money;
}my[maxn];
int cmp(node a,node b){
    return a.l<b.l;
}
int dp[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    while(cin>>n>>m>>R){
        for(int i=1;i<=m;i++){
            cin>>my[i].l>>my[i].r>>my[i].money;
        }
        sort(my+1,my+1+m,cmp);
        int ans=0;
        for(int i=1;i<=m;i++){
            int ma=0;
            for(int j=1;j<i;j++){
                if(my[j].r+R<=my[i].l&&ma<dp[j]){
                    ma=dp[j];
                }
            }
            dp[i]=ma+my[i].money;
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

S.POJ3666 Making the Grade

題意

一個序列A,讓你把它變成不嚴格遞減或者不嚴格遞增的序列B,花費是
\[ \sum abs(b[i]-a[i]) \]

思路

構造DP【i】【j】表示前面i個元素組成的不嚴格遞減和不嚴格遞增序列,其中J是這個序列中的最大值;

然後通過DP【i-1】求出最小值,中間利用了離散化

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=2200;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
//const int inf=0x3f3f3f3f;
ll a[maxn];
ll dp[maxn][maxn];
int n;
int cnt=0;
vector<ll>ve;
ll abs(ll a){
    if(a>=0)return a;
    else return -a;
}
int cmp(int a,int b){return a>b;}
ll ac(){
    for(int i=1;i<=n;i++){
        ll mi=dp[i-1][0];
        for(int j=0;j<cnt;j++){
            mi=min(mi,dp[i-1][j]);
            dp[i][j]=abs(a[i]-ve[j])+mi;
        }
    }
    ll ans=dp[n][0];
    for(int i=0;i<cnt;i++)ans=min(ans,dp[n][i]);
    return ans;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i],ve.push_back(a[i]);
    sort(ve.begin(),ve.end());
    ve.erase(unique(ve.begin(),ve.end()),ve.end());
    cnt=ve.size();
    ll ans1=ac();
    sort(ve.begin(),ve.end(),cmp);
    ll ans2=ac();
    ll anw=min(ans1,ans2);
    cout<<anw<<endl;
    return 0;
}

「kuangbin帶你飛」專題十二 基礎DP