1. 程式人生 > 其它 >DP基礎 0306(VJ)

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;
}