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