1. 程式人生 > >kuangbin專題十二基礎dp總結

kuangbin專題十二基礎dp總結

做這個專題的時候感覺好迷。一度被題噁心到了。。
這題把所有不是獨立思考做出來的題貼出來吧。
A - Max Sum Plus Plus
題解:dp[i][j] 代表前i個數在必須選第i個的前提下組成j組的最大值。
那麼方程為:
dp[i][j]=max(dp[i-1][j]+a[i],dp[k][j-1]+a[i])(k<=i-1&&k>=j-1).
由於n特別大,我們不可能開二維陣列。
其次,我們再看它的複雜度,O(n*m*n),肯定超時。
所以我們要做出這個題首先要解決這兩個問題。
我們想一想,根據它的拓撲序,我們計算的時候可以把i放在第一次,j放在第二層,也可以倒過來放。那麼倒過來放後我們可以發現dp[i][j] 會由它上一個計算的值以及上一層(也就是j-1層)的max(dp[k][j-1] (k<=i-1&&k>=j-1))得出。既然只由這兩個得出,我們就可以只開一個一維陣列dp[n]來存當前層的答案,再開一個mx[n]陣列存上一層的極值即可。
這樣轉換後,空間及時間都不會超了。
注意mx陣列的修改順序,因為當計算第
i個數時,我們需要mx[i-1]的值,所以mx[i-1]的值需要在用完之後才能改。

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int a[maxn], dp[maxn], mx[maxn];

int main()
{
    int m, n;
    while (~scanf("%d %d", &m, &n))
    {
        for (int
i = 1;i <= n;i++)scanf("%d", &a[i]); memset(dp, 0, sizeof(dp)); memset(mx, 0, sizeof(mx)); int Max; for (int j = 1;j <= m;j++) { Max = -inf; for (int i = j;i <= n;i++) { dp[i] = max(dp[i - 1] + a[i], mx[i - 1
] + a[i]); mx[i - 1] = Max; Max = max(Max, dp[i]); } } printf("%d\n", Max); } return 0; }

C - Monkey and Banana
一開始做這個題的時候,想到的是記憶化搜尋,但好像寫搓了,用長寬來做狀態,mle了(現在想想好傻)。
之後就對方塊排個序,就用dp[n]來進行遞推。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int inf = 0x3f3f3f3f;

struct Qube
{
    int x, y, z;
    Qube(int _x, int _y, int _z)
    {
        if (_x < _y)swap(_x, _y);
        x = _x, y = _y, z = _z;
    }
    Qube(){}
    bool operator<(const Qube &b)const
    {
        if (x != b.x)return x > b.x;
        return y > b.y;
    }
}Q[100];
int dp[100];
int main()
{
    int n;
    int cas = 1;
    while (~scanf("%d", &n)&&n)
    {
        int x, y, z;
        int now = 0;
        for (int i = 1;i <= n;i++)
        {
            scanf("%d %d %d", &x, &y, &z);
            Q[++now] = Qube(x, y, z);
            Q[++now] = Qube(x, z, y);
            Q[++now] = Qube(y, z, x);
        }
        sort(Q + 1, Q + 1 + now);
        int ans = 0;
        for (int i = now;i >= 1;i--)
        {
            dp[i] = Q[i].z;
            for (int j = i+1;j <= now;j++)if(Q[i].x>Q[j].x&&Q[i].y>Q[j].y)
            {
                dp[i] = max(dp[i], dp[j] + Q[i].z);
            }
            ans = max(ans, dp[i]);
        }
        printf("Case %d: maximum height = %d\n", cas++, ans);
    }
    return 0;


}

D - Doing Homework
這題一點思路都沒有,之後看題解才發現是狀壓。確實狀壓很有道理。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int inf = 0x3f3f3f3f;
struct node
{
    char str[100];
    int D, C;
}cls[20];
int dp[1 << 15], pre[1 << 15];
int n;
void out(int num)
{
    if (!num)return;
    int tmp;
    for (int i = 0;i < n;i++)
    {
        if (((num >> i) & 1) && !((pre[num] >> i) & 1))
        {
            tmp = i;
            break;
        }
    }
    out(pre[num]);
    printf("%s\n", cls[tmp].str);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {

        scanf("%d", &n);
        memset(dp, inf, sizeof(dp));
        //memset(pre, -1, sizeof(pre));
        for (int i = 0;i < n;i++)
            scanf("%s %d %d", cls[i].str, &cls[i].D, &cls[i].C);
        dp[0] = 0;
        for (int i = 0;i < (1 << n);i++)
        {
            for (int j = 0;j < n;j++)if (!((i >> j) & 1))
            {
                int cnt = 0;
                for (int k = 0;k < n;k++)if ((i >> k) & 1)
                    cnt += cls[k].C;
                cnt += cls[j].C;
                if (cnt > cls[j].D)cnt -= cls[j].D;
                else cnt = 0;
                if (dp[i + (1 << j)] > dp[i] + cnt)
                {
                    dp[i + (1 << j)] = dp[i] + cnt;
                    pre[i + (1 << j)] = i;
                }
            }
        }
        printf("%d\n", dp[(1 << n) - 1]);
        out((1 << n) - 1);
    }
}

I - 最少攔截系統
一開始並不值得怎麼建狀態,看了別人說貪心可以做,覺得很有道理就用貪心寫了。
我們可以用一個數組B來維護每個系統的最後一個值,也就是每個系統最小的值。然後將B從小到大排序(理解一下為什麼從小到大),然後判斷這個值能否加到B裡面的系統中,如果不能就係統數++ 。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
int B[1005];
int main()
{
    int n;
    while (~scanf("%d",&n))
    {
        int bcount = 0;
        int tmp;
        for (int i = 1;i <= n;i++)
        {
            scanf("%d", &tmp);
            int j;
            for (j = 1;j <= bcount;j++)
            {
                if (B[j] >= tmp)
                {
                    B[j] = tmp;
                    break;
                }
            }
            if (j == bcount + 1)
            {
                B[++bcount] = tmp;
            }
            sort(B + 1, B + 1 + bcount);
        }
        printf("%d\n", bcount);
    }
    return 0;
}

寫部落格的時候看別人寫的才發現 它的本質是最長上升子序列,因為最長上升子序列=序列中不上升的序列的個數。(之後會在另一篇部落格詳細說明).

K - Jury Compromise
這題確實比較有意思。一開始建狀態還是不會建,但看完別人的陣列就知道怎麼做了,果然還是不會寫題。。
我們來分析一下別人的做法(錯誤做法):
首先用二維陣列dp[i][j] 來表示取i個人,他們的差為j,和最大的那個方案的和(差就代表D-P,和就代表D+P)。
那麼dp[i][j]=max(dp[i-1][j-del[k]]+sum[k])。
明顯可以發現上述式子有3層迴圈,第一層i,第二層j,再迴圈k。我們再加人的時候又得需要記錄路徑看之前是否已經加過這個人。
一眼看過去確實有點彆扭,但也說不出哪裡有問題。因為這個題目和01揹包有點類似,只不過是加入了選擇個數的限制而已。我們在做01揹包的時候,首先把物品個數放在最前面,這樣就能保證每個階段都是隻考慮一個物品,所以每個物品並不會放兩次。而這題寫法卻和無限揹包一樣,把物品個數放在最下面,這樣毋庸置疑肯定會選重複,為了避免這個出現,他就做一個判斷是否之前選過這個來把這種情況去除掉。但這裡有一些疑問:比如之前已經有一個最優方案選了A方案,但其實選B方案和選A的貢獻是一樣的。那麼下一次我們在找最優方案時,假設一個元素c在A中,它和B方案可以組成最優方案,但B方案的位置已經被A佔走了,那麼就永遠找不到最優方案了。
這裡有一組資料
9 6
6 2
16 10
4 9
19 8
17 12
4 7
10 2
2 14
5 18
0 0
這組資料的答案是

Jury #1

Best jury has value 54 for prosecution and value 54 for defence:

1 2 3 4 6 9

但是錯誤程式的答案是

Jury #1
Best jury has value 52 for prosecution and value 52 for defence:
1 3 4 5 6 8

當正解和錯誤答案dp[5][-4+fixd] 都等於100(正解選擇2,3,4,6,9,而錯誤程式選擇了1,2,4,8,9),所以正解會選上1,變成總和108,但錯誤程式之前就選了1,那麼它永遠找不到這個答案了。
說了那麼多,實際上這題就是01揹包的變形題,按照01揹包的寫法即可做出來。
輸出路徑的程式碼希望大家可以理解理解,這裡不再贅述了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 205;
int D[maxn], P[maxn];
int Deldp[maxn], Sumdp[maxn];
int dp[maxn][25][1000];
int path[maxn][25][1000];

void out(int a, int b, int idx)
{
    if (b == 0)return;
    int now = path[a][b][idx];
    if (a != now)
        out(a - 1, b, idx);
    else
    {
        out(a - 1, b - 1, idx - Deldp[now]);
        printf(" %d", now);
    }
}

int main()
{
    int n, m;
    int cas = 1;
    while (~scanf("%d %d",&n,&m)&&n)
    {
        memset(dp, -1, sizeof(dp));
        int fixd = 400;
        for (int i = 1;i <= n;i++)
        {
            scanf("%d %d", &D[i], &P[i]);
            Deldp[i] = D[i] - P[i];
            Sumdp[i] = D[i] + P[i];
        }
        for (int i = 0;i <= n;i++)
            dp[i][0][fixd] = 0;
    //  dp[0][0][fixd] = 0;
        for (int i = 1;i <= n;i++)
            for(int j=1;j<=min(i,m);j++)
                for (int k = 0;k <= 800;k++)
                {
                    dp[i][j][k] = dp[i - 1][j][k];
                    path[i][j][k] = path[i - 1][j][k];
                    if (k >= Deldp[i] && dp[i - 1][j - 1][k - Deldp[i]] != -1)
                    {

                        if (dp[i][j][k] < dp[i - 1][j - 1][k - Deldp[i]] + Sumdp[i])
                        {
                            dp[i][j][k] = dp[i - 1][j - 1][k - Deldp[i]] + Sumdp[i];

                            path[i][j][k] = i;
                        }

                    }
                    //if (dp[i][j][k] != -1)
                        //printf("%d %d %d %d\n", i, j, k, dp[i][j][k]);
                }

        int i = 0;
        while (dp[n][m][fixd + i] == -1 && dp[n][m][fixd - i] == -1)
        {
            i++;
        }
        int idx = dp[n][m][fixd + i] > dp[n][m][fixd - i] ? fixd + i : fixd - i;
        printf("Jury #%d\n", cas++);
        printf("Best jury has value %d for prosecution and value %d for defence:\n", (dp[n][m][idx] + (idx - fixd)) / 2, (dp[n][m][idx] - ( idx - fixd)) / 2);
        out(n, m, idx);

        puts("");
    }
    return 0;

}

Q - Phalanx
一開始腦子抽了寫了一個三維dp[i][j][k],在第i行j列長度k是否可行。之後想了想實際dp[i][j] 就行了,果然我很菜。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
using namespace std;
char map[1005][1005];
int dp[1005][1005];
int n;
int check(int x, int y)
{
    int num = 0;
    while (y-num>=1&&x+num<=n&&map[x][y - num] == map[x + num][y])
        num++;
    return num;
}
int main()
{

    while (~scanf("%d",&n)&&n)
    {
        memset(dp, 0, sizeof(dp));
        for (int i = 1;i <= n;i++)
            scanf("%s", map[i] + 1);
        //for (int i = 1;i <= n;i++)dp[i][1] = dp[n][i] = 1;
        int ans = 0;
        for(int i=n;i>=1;i--)
            for (int j = 1;j <= n;j++)
            {
                int tmp = check(i, j);
                if (tmp > dp[i + 1][j - 1])
                    dp[i][j] = dp[i + 1][j - 1] + 1;
                else
                    dp[i][j] = tmp;
                ans = max(ans, dp[i][j]);
            }
        //if (ans == 1)ans = 0;
        printf("%d\n", ans);
    }
}

S - Making the Grade
挺好的題,一開始同樣沒想到如何遞推。。
我們先做非嚴格遞增的情況。
設dp[i][j] 為把第i個數變成j的最小价值。
那麼遞推方程為:
dp[i][j] = abs(a[i]-j)+min(dp[i-1][k])(k<=j)
如果理解了A題,是不是感覺似曾相識。
我們同樣面臨兩個問題,一是陣列太大開不下去,而是複雜度O(n*1e9*1e9),肯定不可行。
首先我們可以用A題的做法把最裡面的一層優化掉,那麼複雜度為O(N*1e9),仍然不可行,該怎麼辦呢。
接下來的定理博主並不能證明,但可以大概講解其中的奧妙。
假如資料為10 3,那麼我們要麼把10變成3,要麼把3變成10。花費為7。那麼當資料為10 7 4呢,讀者可以思考一下,多試幾組資料就會發現修改完後的非嚴格遞增序列的數都為原來序列中的數。為什麼會這樣呢,大家可以畫一個一維座標軸,然後原來的序列的數會把座標軸分成很多塊,我們在比較兩個數的時候往往帶一個等號,那麼其中一個數只會向他靠攏,而不會超過他,最小花費也就是把這個數變成另一個在序列中的數。
所以既然有這個定理,那麼這個問題就迎刃而解啦,只要把數字離散化一下,不論時間還是空間的問題都被解決了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>

using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f;

int a[2005],b[2005];
ll dp[2005][2005];
int main()
{
    int N;
    scanf("%d", &N);
    for (int i = 1;i <= N;i++)scanf("%d", &a[i]), b[i] = a[i];
    sort(a + 1, a + 1 + N);
    int Size = unique(a + 1, a + 1 + N) - a - 1;
    for (int i = 1;i <= N;i++)
    {
        ll mn = inf;
        for (int j = 1;j <= Size;j++)
        {
            if (i != 1)
            {
                mn = min(mn, dp[i - 1][j]);
                dp[i][j] = abs(b[i] - a[j]) + mn;
            }
            else
                dp[i][j] = abs(b[i] - a[j]);
        }
    }
    ll ans = inf;
    for (int i = 1;i <= Size;i++)
        ans = min(ans, dp[N][i]);
    cout << ans << endl;

}

博主在做這個專題時,一度心態爆炸,在做K題時寫了3個小時,中途一直想放棄這個題,但明明知道怎麼做,就是越寫越慢,越寫越慢。。
寫dp時,博主常常糾結2個東西,一是初始化,二是式子。
dp還需要加強啊!!

相關推薦

kuangbin專題基礎dp總結

做這個專題的時候感覺好迷。一度被題噁心到了。。 這題把所有不是獨立思考做出來的題貼出來吧。 A - Max Sum Plus Plus 題解:dp[i][j] 代表前i個數在必須選第i個的前提下組成j組的最大值。 那麼方程為: dp[i][j]=ma

kuangbin帶你飛」專題 基礎DP

bank 結束 cut maximum 狀態 you else if tao 全部 layout: post title: 「kuangbin帶你飛」專題十二 基礎DP author: "luowentaoaa" catalog: true tags:

kuangbin專題 基礎DP1【從入門到熟練】【9+1題】

HDU1024 Max Sum Plus Plus 感覺這題是整個系列裡難度最高的題之一? #include<bits/stdc++.h> #include<stdio.h> #include<iostream> #include<algor

【 題集 】 【kuangbin帶你飛】專題 基礎DP1

Nowadays, a kind of chess game called “Super Jumping! Jumping! Jumping!” is very popular in HDU. Maybe you are a good boy, and know little about this game,

kuangbin專題 DP專題 HDU1024 最大m子序列和

題意: 給你n個數,然後讓你在裡面找到m個子序列,讓這m個子序列的和最大。其中可以不要一些數,但是這些子序列裡面的數必須是連續的。 題解: 這道題是我做kuangbin大神的專題的第一道題,當然就被嚇到不敢去做,今天用了快一天的時間把這道題

[kuangbin帶你飛]專題 基礎DP1

A - Max Sum Plus Plus 求一個數列中取m個不相交子序列所取得的最大值。 dp[i][j]表示取i個子序列且最後一個序列的末尾為j時取得的最大值,所以對於任意一個j只有兩種情況,單獨形成一個新序列的開頭或者加入上一個序列。 #incl

kuangbin專題 HDU1176 免費餡餅

之前的dp專題有這道題,當時沒有寫出來,剛才第一次寫了正推的程式碼。沒有考慮到只能從5開始,WA。後來改了倒推。改了中間的小bug,過了。 思路:在一個點,可以接到 左中右 三個位置的餡餅,為了避免邊界處理,把可能下落的點0~10變為  1~11。 這樣1的 左中右 就

kuangbin專題七AC自動機總結

這個專題寫的我頭皮發麻,出現了好多小bug耗費了我好多時間,但總體看不算太難,只要把思路縷清就行了。 AC自動機的題目有兩類,一類是字串找子串個數的,另一類則是建立狀態,然後進行dp或者矩陣快速冪。 B - 病毒侵襲 這題不算太難,但有一個坑點,就是字元都

DP動態規劃專題:LeetCode 940. Distinct Subsequences II

LeetCode 940. Distinct Subsequences II Given a string S, count the number of distinct, non-empty subsequences of S . Since the result may be lar

[C#基礎知識系列]專題:迭代器

引言:    在C# 1.0中我們經常使用foreach來遍歷一個集合中的元素,然而一個型別要能夠使用foreach關鍵字來對其進行遍歷必須實現IEnumerable或IEnumerable<T>介面,(之所以來必須要實現IEnumerable這個介面,是因

kuangbin帶你飛 - 專題五 - 數位dp

還要 pri splay ems closed 道理 ans 位數 alt https://vjudge.net/contest/70324 A - Beautiful numbers 統計區間內的,被數位上各個為零數字整除的數的個數。 下面是暴力的數位dp寫法,絕對

201521123116 《java程序設計》第周學習總結

textfile objects test tput bsp cti 目的 字節 指定 1. 本周學習總結 1.1 以你喜歡的方式(思維導圖或其他)歸納總結多流與文件相關內容。 2. 書面作業 Q1.字符流與文本文件:使用 PrintWriter(寫),BufferedR

UI標簽庫專題:JEECG智能開發平臺 ckeditor(ckeditor插件標簽)

uicolor nbsp size mar als ott family data- ack ??1. ckeditor(ckeditor插件標簽)1.1. 參數屬性名類型描寫敘述是否必須默認值namestring屬性名稱是nullvaluestring默認值否nulli

基礎DP總結

鏈接 strong else 二分 子序列 邊界 ring sizeof ans ---恢復內容開始--- 基礎DP總結 關鍵詞:基礎DP問題,LIS,LCS,狀壓DP 簡析 :DP大法好啊,當一個大問題不好解決的時候,我們研究它與其子問題的聯系,然後子問題又找它的子問題,

『進階DP專題DP初步』

<更新提示> <第一次更新> <正文> 二維動態規劃 初步 二維動態規劃並不是指動態規劃的狀態是二維的,而是指線性動態規劃的拓展,由線性變為了平面,即在一個平面上做動態規劃。 例題 馬攔過河卒 題目描述

201771010135 楊蓉慶《面對物件程式設計(java)》第週學習總結

1、實驗目的與要求 (1) 掌握Java GUI中框架建立及屬性設定中常用類的API; (2) 掌握Java GUI中2D圖形繪製常用類的API; (3) 瞭解Java GUI中2D圖形中字型與顏色的設定方法; (4) 瞭解Java

徐思201771010132《面向物件程式設計(Java)》第週學習總結

一、理論知識部分 Java的抽象視窗工具箱(Abstract Window Toolkit, AWT)包含在java.awt包中,它提供了許多用來設計GUI的元件類和容器類。 大部分AWT元件都有其Swing的等價元件,Swing元件的名字一般是在AWT元件名前面新增一個字母“J”。 通常把由Compo

徐思201771010132《面向對象程序設計(Java)》第周學習總結

bound 大學 closed color 獲得 ase extends -c 嵌套 一、理論知識部分 Java的抽象窗口工具箱(Abstract Window Toolkit, AWT)包含在java.awt包中,它提供了許多用來設計GUI的組件類和容器類。 大部分AWT

王穎奇 20171010129《面向物件程式設計(java)》第週學習總結

實驗十二  圖形程式設計 理論: 10.1 AWT與Swing簡介 10.2 框架的建立10.3 圖形程式設計10.4 顯示影象 (具體學習總結在最後) 實驗: 實驗時間 2018-11-14 1、實驗目的與要求 (1) 掌握Java GUI中

201771010108 -韓臘梅-第週學習總結

第十二週實驗及總結 一、知識點總結 1、swing概述:swing基於AWT架構之上,Swing是指被繪製的使用者界、面類,AWT是指像事件處理這樣的視窗工具箱的底層機制,Swing可以讓使用者隨意的選擇喜歡的感官。 2、框架:沒有包含在其他視窗中的視窗被稱為框架(frame),在AWT中有一個Fram