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;
}
posted @ 2022-03-07 18:00  _yolanda  阅读(135)  评论(0编辑  收藏  举报