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]\) (上一次从右边取)和 \(f[i-1][i+j-n-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;
}