『進階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的定義,我們接下來考慮如何解決。
既然兩個物件的決策是互相影響的,我們不妨設一個二維狀態
其意義就是如果當前這兩個元素不相等,那麼從上兩個決策中選最優,如果相等,那麼該元素可以組成最長公共子序列的一部分,更新狀態。
那麼 的時間解決該問題。
程式碼實現如下:
#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
分析
這道題也是顯而易見的雙程序入門題,和最長公共子序列相似。對於狀態的設定顯而易見,
代表UP串匹配到i,DOWN串匹配到j的最大匹配數。那麼顯然一開始
。那麼我們依據題面考慮情況:
①UP[i]=DOWN[j],不能進行匹配。
②UP[i]≠DOWN[j],我們在兩個序列中找到上一個出現對應元素的位置,由於不能再次交錯,匹配數從上一個位置轉移而來,匹配數加2。
可得狀態轉移方程如下:
怎麼找上一個對應元素出現的位置呢,對於這道題,暴力查詢即可解決。
程式碼實現如下:
#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:效仿揹包問題
仔細讀題,是不是發現這道題很像揹包問題?只不過他有兩個揹包,物品花費的是限定的時間段罷了。事實上,我們可以效仿揹包寫法,設定狀態
代表前i種草藥,第一個坩堝用時j,第二個坩堝用時k的最大價值。那麼這樣就兼顧了題面的雙程序本質。
考慮狀態轉移,對於每一個草藥的決策,有三種情況:
①不用該草藥
②放入坩堝1
③放入坩堝2
那麼狀態轉移方程根據這三種情況轉移即可: