DP基礎 0306(VJ)
T1(Common Subsequence)
最長公共子序列模板題
\(code:\)
#include<cstring> #include<iostream> #include<cmath> using namespace std; int f[1005][1005]; int main(){ string a,b; while(cin>>a>>b){ memset(f,0,sizeof f); int la=a.size(),lb=b.size(); for(int i=0;i<la;++i){ for(int j=0;j<lb;++j){ if(a[i]==b[j]) f[i+1][j+1]=f[i][j]+1; else f[i+1][j+1]=max(f[i][j+1],f[i+1][j]); } } cout<<f[la][lb]<<endl; } return 0; }
T2(Help Jimmy)
解法: 先按高度將平臺排序,把初始點作為一個長度為1的平臺(為了方便)。
從上倒下掃一遍,只用關注左右端點。
令 \(f[i][0]\) 表示到第 \(i\) 個平臺的左端點最少時間,\(f[i][1]\) 表示第 \(i\) 個平臺右端點最少時間。
因為每個端點跳下去到達的平臺及位置只有一種情況,所以用每個平臺更新其能到達的平臺左右端點的值。
\(code:\)
#include<cstring> #include<iostream> #include<cmath> #include<algorithm> using namespace std; int f[1005][2]; int xx,yy,mh; struct node{ int l,r,h; }a[1005]; bool cmp(node x,node y){ return x.h>y.h; } int main(){ int t; cin>>t; while(t--){ memset(f,0x3f,sizeof f); int n;cin>>n>>xx>>yy>>mh; f[1][0]=f[1][1]=0; a[1].l=xx,a[1].r=xx,a[1].h=yy; for(int i=2;i<=n+1;++i) cin>>a[i].l>>a[i].r>>a[i].h; sort(a+1,a+n+2,cmp); int ans=0x3f3f3f3f; for(int i=1;i<=n+1;++i){ // cout<<i<<" : "<<endl; int ll=a[i].l,rr=a[i].r; bool mrk1=0,mrk2=0; //左 for(int j=i+1;j<=n+1;++j){ if(a[j].h>=a[i].h) continue; if(a[i].h-a[j].h>mh) break; if(a[j].l<=ll && a[j].r>=ll){ // cout<<"l: "<<j<<endl; mrk1=1; f[j][0]=min(f[i][0]+ll-a[j].l,f[j][0]); f[j][1]=min(f[i][0]+a[j].r-ll,f[j][1]); break; } } //右 for(int j=i+1;j<=n+1;++j){ if(a[j].h>=a[i].h) continue; if(a[i].h-a[j].h>mh) break; if(a[j].l<=rr && a[j].r>=rr){ // cout<<"r: "<<j<<endl; mrk2=1; f[j][0]=min(f[i][1]+rr-a[j].l,f[j][0]); f[j][1]=min(f[i][1]+a[j].r-rr,f[j][1]); break; } } if(!mrk1 && a[i].h<=mh) ans=min(ans,f[i][0]); if(!mrk2 && a[i].h<=mh) ans=min(ans,f[i][1]); } cout<<ans+yy<<endl; } return 0; }
T3(Treats for the Cows)
題意: 一個序列,每次從左端或右端取一個數,得到的值是 第幾次取的 $ $ 當前數的值*
正解: 區間dp。\(f[l][r]\) 表示在 \(l\) 到 \(r\) 這個區間可以獲得的最大值。
更新一個區間的時候列舉斷點。
我的做法: 普通dp。\(f[i][j][0/1]\) 表示第 \(i\) 次取第 \(j\) 個數時從 左/右 取的最大值。
\(f[i][j][0]\) 可以從 \(f[i-1][j-1][0]\) (上一次從左邊取)和 \(f[i-1][n-i+j+1][1]\) (上一次從右邊取)更新。
\(f[i][j][0]\) 可以從 \(f[i-1][j+1][0]\)
\(code:\)
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<stdio.h>
using namespace std;
int a[2005];
long long f[2005][2005][2];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
memset(f,-1,sizeof f);
for(int i=0;i<=n;++i) f[0][i][0]=f[0][i][1]=0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
//左端
if(j<=i){
if(f[i-1][j-1][0]!=-1)
f[i][j][0]=max(f[i][j][0],f[i-1][j-1][0]+a[j]*i);
if(f[i-1][n-i+j+1][1]!=-1)
f[i][j][0]=max(f[i][j][0],f[i-1][n-i+j+1][1]+a[j]*i);
}
//右端
if(i>=n-j+1){
if(f[i-1][j+1][1]!=-1)
f[i][j][1]=max(f[i][j][1],f[i-1][j+1][1]+a[j]*i);
if(f[i-1][i+j-n-1][0]!=-1)
f[i][j][1]=max(f[i][j][1],f[i-1][i+j-n-1][0]+a[j]*i);
}
}
}
long long ans=-1;
for(int i=1;i<=n;++i)
ans=max(max(ans,f[n][i][1]),f[n][i][0]);
printf("%lld",ans);
return 0;
}
T4(Milking Time)
題意: 給定 \(m\) 個區間(可重疊)。選出若干個個區間,每兩個相鄰區間的時間間隔(後一個的左-前一個的右)至少為 \(r \)。每個區間的右端點不超過 \(n\)。
解法: 先按右端點排序,令 \(f[i]\) 表示 \(1-i\) 可獲得的最大值。
每次用該區間 左端點-r 以前的 \(f\) 的最大值來更新。可以暴力的,資料範圍比較小,我用的樹狀陣列。
\(code:\)
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<stdio.h>
using namespace std;
struct node{
int l,r,w;
}a[1005];
int n;
int ma[1000005];
int cmp(node x,node y){
return x.r<y.r;
}
int lw(int x){
return x&-x;
}
void change(int pos,int x){
for(int i=pos;i<=n;i+=lw(i))
ma[i]=max(ma[i],x);
}
int ask(int x){
int res=-1;
for(int i=x;i>0;i-=lw(i)) res=max(res,ma[i]);
return res;
}
int main(){
int m,rr;
cin>>n>>m>>rr;
for(int i=1;i<=m;++i)
cin>>a[i].l>>a[i].r>>a[i].w;
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;++i){
int xx=0;
if(a[i].l>=rr) xx=ask(a[i].l-rr);
change(a[i].r,xx+a[i].w);
}
cout<<ask(n);
return 0;
}
T5(Making the Grade)
題意: 一個序列,每次可以給每個數加上或減去一些值(每次操作消耗為加上或減去的值),使得這個序列非嚴格遞增/遞減。問達到目標的最小消耗。
解法: 有個結論——最後的序列中每個數一定是原序列中的數。
先離散化並去重原序列。令 \(f[i][j]\) 表示前 \(i-1\) 個數滿足條件並且第 \(i\) 個數為離散化後的第 \(j\) 個數是的最小值。
\(f[i][j]\) 由 \(f[i-1][1\ to \ j]\) (以單增為例)中的最小值更新而來。
\(code:\)
#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstring>
#include<stdio.h>
using namespace std;
long long f[2005][2005],b[2005],a[2005];
long long mi[2005];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
int m=unique(b+1,b+n+1)-b-1;
memset(f,0x3f,sizeof f);
mi[0]=0x3f3f3f3f;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
f[i][j]=mi[j]+abs(int(a[i]-b[j]));
mi[j]=min(mi[j-1],f[i][j]);
}
long long ans=0x3f3f3f3f;
for(int i=1;i<=m;++i) ans=min(ans,mi[i]);
printf("%lld",ans);
return 0;
}