DP练习4-0403(vj)

比赛地址

T1(饭卡)

背包

\(m-5\) 为总容积,进行01背包

点击查看代码
#include<iostream>
#include<algorithm>
#include<math.h>
#include<string.h>
#include<stdio.h>
using namespace std;

bool f[2005];
int a[1005];

int main(){
	
	int n;
	while(cin>>n && n){
		memset(f,0,sizeof f);
		int m;
		const int sum=60;
		for(int i=1;i<=n;++i)	cin>>a[i];
		sort(a+1,a+n+1);
		cin>>m;
		if(m<5){
			cout<<m<<endl;
			continue;
		}
		f[0]=1;
		for(int i=1;i<n;++i)
			for(int j=m-5;j>=a[i];--j)	f[j]|=f[j-a[i]];
		for(int i=m-5;i>=0;--i){
			if(f[i]){
				cout<<m-i-a[n]<<endl;
				break;
			}
		}
	}
	
	return 0;
} 

T2(Cheapest Palindrome)

题意:给定一个字符串S,字符串S的长度为M(M≤2000),字符串S所含有的字符的种类的数量为N(N≤26),然后给定这N种字符Add与Delete的代价,求将S变为回文串的最小代价和。

区间DP

\[f[l][r]=\begin{cases}f[l+1][r-1]&s[l]=s[r] \\ min(f[l+1][r]+min(del[l],add[l]),f[l][r-1]+min(del[r],add[r])) & s[l]!=s[r]\end{cases} \]

点击查看代码
#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;

int f[2005][2005];
int add[30],del[30];

int main(){
	
	int n,m;
	char s[2005];
	cin>>n>>m>>s+1;
	memset(add,0x3f,sizeof add);
	memset(del,0x3f,sizeof del);
	for(int i=1;i<=n;++i){
		char a;cin>>a;
		cin>>add[a-'a']>>del[a-'a'];
	}
	
	for(int len=1;len<=m;++len){
		for(int l=1;l<=m-len+1;++l){
			int r=l+len-1;
			if(l==r)	continue;
			if(s[l]==s[r])	f[l][r]=f[l+1][r-1];
			else{
				f[l][r]=f[l+1][r]+min(del[s[l]-'a'],add[s[l]-'a']);
				f[l][r]=min(f[l][r],f[l][r-1]+min(del[s[r]-'a'],add[s[r]-'a']));
			}
		}
	} 
	
	cout<<f[1][m];
	
	return 0;
}

T3(Doing Homework)

题意:有n个任务,每个任务有一个截止时间,超过截止时间一天,要扣一个分。
求如何安排任务,使得扣的分数最少。

数据很小,于是状压DP

先处理每个状态的耗时,再转移就好。另外记录方案,因为输入按字典序,最后着递归找答案,所以倒序循环,遇到的最小值第一个就是使他转移的编号

点击查看代码
#include<bits/stdc++.h>
using namespace std;

int n,f[100005],come[100004],sum[100005];
struct node{
	string name;
	int d,s;
}a[20];

void init(){
	memset(f,0x3f,sizeof f);
	memset(come,0x3f,sizeof come);
	memset(sum,0,sizeof sum);
	for(int i=1;i<(1<<n);++i)
		for(int j=0;j<n;++j)
			sum[i]+=((i>>j)&1)*a[j+1].s;
}
void solve(){
	init();
	int m=1<<n;f[0]=0;
	for(int i=1;i<m;++i)
		for(int j=n-1;j>=0;--j)
			if((i>>j)&1){
				if(f[i-(1<<j)]+max(0,sum[i]-a[j+1].d)<f[i]){
					f[i]=f[i-(1<<j)]+max(0,sum[i]-a[j+1].d);
					come[i]=j+1; 
				}
			}
				
	int k=m-1,co=0;
	int ans[20]={0};
	cout<<f[m-1]<<endl;
	while(k){
		ans[++co]=come[k];
		k-=(1<<(come[k]-1));
	}
	for(int i=co;i>=1;--i)	cout<<a[ans[i]].name<<endl;
}

int main(){
	
	int t;cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;++i)	cin>>a[i].name>>a[i].d>>a[i].s;
		solve();
	}
	
	return 0;
}

T4(Print Article)

题意:给出 N 个单词,每个单词有个非负权值 \(C_i\) ,现在要将它们分成连续的若干段,每段的代价为此段单词的权值和的平方,还要加一个常数 M 。现在想求出一种最优方案,使得总费用之和最小。

斜率优化DP

\[f[i]=min(f[j]+(sum[i]-sum[j])^2+M) \]

\[f[j]+sum[j]^2=2*sum[i]*sum[j]+f[i]-M-sum[i]^2 \]

于是把 \(f[j]+sum[j]^2\) 作为 \(y\) 轴,\(sum[j]\)\(x\) 轴,维护最小值即可。

点击查看代码
//f[i]=min(f[j]+(sum[i]-sum[j])^2+M)
//f[j]+sum[j]^2=2*sum[i]*sum[j]+f[i]-M-sum[i]^2
#include<bits/stdc++.h>
using namespace std;
#define int long long

const int N=5e5+5;
int f[N],c[N],q[N];

signed main(){
	
	int n,m;
	while(cin>>n>>m && n!=EOF){
		for(int i=1;i<=n;++i){
			int cc;cin>>cc;
			c[i]=c[i-1]+cc;
		}
		
		memset(f,0x3f,sizeof f);f[0]=0;
		int l=0,r=0;
		for(int i=1;i<=n;++i){
			int k=2*c[i];
			while(l<r && (f[q[l+1]]+c[q[l+1]]*c[q[l+1]]-f[q[l]]-c[q[l]]*c[q[l]])<=k*(c[q[l+1]]-c[q[l]]))
				++l;
			int j=q[l];
			f[i]=f[j]+(c[i]-c[j])*(c[i]-c[j])+m;
			while(l<r && (f[i]+c[i]*c[i]-f[q[r]]-c[q[r]]*c[q[r]])*(c[q[r]]-c[q[r-1]])<=(f[q[r]]+c[q[r]]*c[q[r]]-f[q[r-1]]-c[q[r-1]]*c[q[r-1]])*(c[i]-c[q[r]]))
				--r;
			q[++r]=i;
		}
		cout<<f[n]<<endl;
	}
	
	return 0;
}
posted @ 2022-04-03 19:26  _yolanda  阅读(28)  评论(0编辑  收藏  举报