1. 程式人生 > >『進階DP專題:雙程序DP初步』

『進階DP專題:雙程序DP初步』


<更新提示>

<第一次更新>昨天老劉口胡了一波dp,看到除了高階dp以外,自己竟然還有一塊dp基本沒碰過,然後趕快自學了一晚上,總結成部落格如下。


<正文>

雙程序類動態規劃

初步

顧名思義,就是在動態規劃時需要對兩個物件同時進行決策,而兩個決策並不是互相獨立的,而是互相影響的。

例題

最長公共子序列

題目描述

字元序列的子序列是指從給定字元序列中隨意地(不一定連續)去掉若干個字元(可能一個也不去掉)後所形成的字元序列。 令給定的字元序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一個嚴格遞增下標序 < i0,i1,i2,…,ik-1 >,使得對所有的j=0,1,…,k-1,有xij = yj。

例如,X=“ABCBDAB”,Y=“BCDB”是X的一個子序列。

對給定的兩個字元序列,求出他們最長的公共子序列
輸入格式

第1行為第1個字元序列,都是大寫字母組成,以”.”結束。長度小於5000。 第2行為第2個字元序列,都是大寫字母組成,以”.”結束,長度小於5000。
輸出格式

輸出上述兩個最長公共自序列的長度。
樣例資料

input

ABCBDAB.
BACBBD.

output

4

資料規模與約定

時間限制:1s

空間限制:256MB

分析

這是一道雙程序類dp的入門題,也可以說是序列dp的入門題。其中,關鍵的字眼“他們”,“公共子序列”我已經在題面裡標出,代表了這是一道雙程序類的dp題。“他們”代表有兩個物件,“公共子序列”代表決策互相影響,符合雙程序類dp的定義,我們接下來考慮如何解決。
既然兩個物件的決策是互相影響的,我們不妨設一個二維狀態

f [ i ] [ j ] 代表序列X的前i位,序列Y的前j位當中最長公共子序列的長度。那麼我們就可以利用兩重迴圈列舉i,j來對狀態進行轉移,其狀態轉移方程如下:

1][j],f[i][j&#x2212;1])(x[i]&#x2260;y[j])f[i&#x2212;1][j&#x2212;1]+1(x[i]=y[j])" role="presentation"> f [ i ] [ j ] = { m a x ( f [ i 1 ] [ j ] , f [ i ] [ j 1 ] ) ( x [ i ] y [ j ] ) f [ i 1 ] [ j 1 ] + 1 ( x [ i ] = y [ j ] )
其意義就是如果當前這兩個元素不相等,那麼從上兩個決策中選最優,如果相等,那麼該元素可以組成最長公共子序列的一部分,更新狀態。
那麼 O ( n 2 ) 的時間解決該問題。

程式碼實現如下:

#include<bits/stdc++.h>
using namespace std;
string s1,s2;
char a[5050],b[5050];
int f[5050][5050]={},maxx=0;
int main()
{
    cin>>s1>>s2;
    for (int i=0;i<s1.length();i++)
        a[i+1]=s1[i];
    for (int i=0;i<s2.length();i++)
        b[i+1]=s2[i];
    for(int i=1;i<=s1.length();i++)
    {
        for(int j=1;j<=s2.length();j++)
        {
            if (a[i]==b[j])f[i][j]=f[i-1][j-1]+1;
            else f[i][j]=max(f[i-1][j],f[i][j-1]);
            maxx=max(f[i][j],maxx);
        }
    }
    cout<<maxx<<endl;
    return 0;
} 
交錯匹配

題目描述

兩行自然數,UP[1..N],DOWN[1..M],如果UP[I]=DOWN[J]=K,那麼上行的第I個位置的數就可以跟下行的第J個位置的數連一條線,稱為一條K匹配,但是同一個位置的數最多隻能連一條線。

另外,每個K匹配都必須且至多跟一個L匹配相交且K≠L 。現在要求一個最大的匹配數。

例如:以下兩行數的最大匹配數為8
這裡寫圖片描述
輸入格式

第一行有兩個正整數N和M。第二行N個UP的自然數,第三行M個DOWN的自然數。其中0 < N、M<=200,UP、DOWN的數都不超過32767。
輸出格式

一個整數:最大匹配數
樣例資料

input1

12 11
1 2 3 3 2 4 1 5 1 3 5 10
3 1 2 3 2 4 12 1 5 5 3

output1

8

input2

4 4
1 1 3 3
1 1 3 3

output2

0

資料規模與約定

時間限制:1s

空間限制:256MB

分析

這道題也是顯而易見的雙程序入門題,和最長公共子序列相似。對於狀態的設定顯而易見, f [ i ] [ j ] 代表UP串匹配到i,DOWN串匹配到j的最大匹配數。那麼顯然一開始 f [ i ] [ j ] = m a x ( f [ i 1 ] [ j ] , f [ i ] [ j 1 ] ) 。那麼我們依據題面考慮情況:
①UP[i]=DOWN[j],不能進行匹配。
②UP[i]≠DOWN[j],我們在兩個序列中找到上一個出現對應元素的位置,由於不能再次交錯,匹配數從上一個位置轉移而來,匹配數加2。
可得狀態轉移方程如下:

f [ i ] [ j ] = m a x ( f [ i 1 ] [ j ] , f [ i ] [ j 1 ] ) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ l a s t I ] [ l a s t J ] + 2 ) ( U P [ i ] D O W N [ j ] )
怎麼找上一個對應元素出現的位置呢,對於這道題,暴力查詢即可解決。

程式碼實現如下:

#include<bits/stdc++.h>
using namespace std;
inline void read(int &k)
{
    int x=0,w=0;char ch;
    while(!isdigit(ch))w|=ch=='-',ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    k=(w?-x:x);return;
}
int n,m,a[280]={},b[280]={},f[280][280]={};
inline void input()
{
    read(n),read(m);
    for(int i=1;i<=n;i++)read(a[i]);
    for(int i=1;i<=m;i++)read(b[i]);
}
inline int findlast(int list[],int last,int value)
{
    for(int i=last-1;i>=1;i--)
    {
        if(list[i]==value)return i;
    }
    return 0;
}
inline void solve()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j])continue;
            int lastI=findlast(a,i,b[j]);
            int lastJ=findlast(b,j,a[i]);
            if(lastI&&lastJ)
            {
                f[i][j]=max(f[i][j],f[lastI-1][lastJ-1]+2);
            }
        }
    }
}
inline void output()
{
    cout<<f[n][m]<<endl;
}
int main()
{
    input();
    solve();
    output();
    return 0;
}
配置魔藥

題目描述

在《Harry Potter and the Chamber of Secrets》中,Ron的魔杖因為坐他老爸的Flying Car撞到了打人柳,不幸被打斷了,從此之後,他的魔杖的魔力就大大減少,甚至沒辦法執行他施的魔咒,這為Ron帶來了不少的煩惱。

這天上魔藥課,Snape要他們每人配置一種魔藥(不一定是一樣的),Ron因為魔杖的問題,不能完成這個任務,他請Harry在魔藥課上(自然是躲過了Snape的檢查)幫他配置。 現在Harry面前有兩個坩堝,有許多種藥材要放進坩堝裡,但坩堝的能力有限,無法同時配置所有的藥材。一個坩堝相同時間內只能加工一種藥材,但是不一定每一種藥材都要加進坩堝裡。加工每種藥材都有必須在一個起始時間和結束時間內完成(起始時間所在的那一刻和結束時間所在的那一刻也算在完成時間內),每種藥材都有一個加工後的藥效。

現在要求的就是Harry可以得到最大的藥效。
輸入格式

輸入檔案的第一行有2個整數,一節魔藥課的t(1≤t<≤500)和藥材數n(1≤n≤100)。

輸入檔案第2行到n+1行中每行有3個數字,分別為加工第i種藥材的起始時間t1、結束時間t2、(1≤t1≤t2≤t)和藥效w(1≤w≤100)。
輸出格式

只有一行,只輸出一個正整數,即為最大藥效。
樣例資料

input

7 4
1 2 10
4 7 20
1 3 2
3 7 3

output

35

【註釋】本題的樣例是這樣實現的:第一個坩堝放第1、4種藥材,第二個坩堝放第2、3種藥材。這樣最大的藥效就為10+20+2+3=35。
資料規模與約定

對於30%的資料 1<=t<=500 1<=n<=15 1<=w<=100 1<=t1<=t2<=t

對於100%的資料 1<=t<=500 1<=n<=100 1<=w<=100 1<=t1<=t2<=t

時間限制:1s

空間限制:256MB

分析

“兩個坩堝”代表了這道題的雙程序本質。對於草藥,每一個草藥都有時間先後的問題,我們將他們按照時間排一下序,就能有序的解決問題。那麼剩下的問題該如何解決呢?
解法1:效仿揹包問題
仔細讀題,是不是發現這道題很像揹包問題?只不過他有兩個揹包,物品花費的是限定的時間段罷了。事實上,我們可以效仿揹包寫法,設定狀態 f [ i ] [ j ] [ k ] 代表前i種草藥,第一個坩堝用時j,第二個坩堝用時k的最大價值。那麼這樣就兼顧了題面的雙程序本質。
考慮狀態轉移,對於每一個草藥的決策,有三種情況:
①不用該草藥
②放入坩堝1
③放入坩堝2
那麼狀態轉移方程根據這三種情況轉移即可: