kuangbin专题十二 基础dp
1. Max Sum Plus Plus
压缩一维避免超空间,维护前缀最大值将 O ( n 3 ) O(n^3) O(n3)降到 O ( n 2 ) O(n^2) O(n2)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,INF=0x7fffffff;
int n,m;
int a[N],f[N],Max[N];
int main()
{
while(scanf("%d%d",&m,&n)!=EOF){
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=0;i<=n;i++) f[i]=Max[i]=0;
int cur;
for(int j=1;j<=m;j++){
cur=-INF;
for(int i=j;i<=n;i++){
f[i]=max(f[i-1],Max[i-1])+a[i];
Max[i-1]=cur;
cur=max(cur,f[i]);
}
}
printf("%d\n",cur);
}
return 0;
}
2. Ignatius and the Princess IV
感觉像是题目放错了,用数组或者map记录个数就行。
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
const int N=1e6+10;
int n,a[N];
unordered_map<int,int> num;
int main()
{
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
num.clear();
for(int i=1;i<=n;i++){
num[a[i]]++;
if(num[a[i]]>=(n+1)/2){
printf("%d\n",a[i]);
break;
}
}
}
return 0;
}
相邻的HDU 1028刚好是个dp。分别可以用完全背包或分治的做法来做。
#include<bits/stdc++.h>
using namespace std;
const int N=130;
int f[N];
void init(){
f[0]=1;
for(int i=1;i<N;i++)
for(int j=i;j<N;j++)
f[j]+=f[j-i];
}
int main()
{
init();
int n;
while(scanf("%d",&n)!=EOF){
printf("%d\n",f[n]);
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=130;
int f[N][N];
int calc(int a,int b){
if(f[a][b]!=-1) return f[a][b];
if(a<1||b<1) return f[a][b]=0;
if(a==1||b==1) return f[a][b]=1;
if(a==b) return f[a][b]=1+calc(a,b-1);
if(a<b) return f[a][b]=calc(a,a);
if(a>b) return f[a][b]=calc(a-b,b)+calc(a,b-1);
}
int main()
{
memset(f,-1,sizeof f);
int n;
while(scanf("%d",&n)!=EOF){
printf("%d\n",calc(n,n));
}
return 0;
}
3. Monkey and Banana
最长上升子序列模型题
#include<bits/stdc++.h>
using namespace std;
const int N=200;
struct Block{
int x,y,z;
bool operator >(const Block &t)const{
if(x!=t.x) return x>t.x;
return y>t.y;
}
};
int f[N];
int n;
int main()
{
int T=0;
while(scanf("%d",&n),n){
vector<Block> v;
for(int i=1;i<=n;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
v.push_back({a,b,c});
v.push_back({a,c,b});
v.push_back({b,a,c});
v.push_back({b,c,a});
v.push_back({c,a,b});
v.push_back({c,b,a});
}
sort(v.begin(),v.end(),greater<Block>());
int res=0;
for(int i=0;i<v.size();i++){
f[i]=v[i].z;
for(int j=0;j<i;j++){
if(v[i].x<v[j].x&&v[i].y<v[j].y){
f[i]=max(f[i],f[j]+v[i].z);
}
}
res=max(res,f[i]);
}
printf("Case %d: maximum height = %d\n",++T,res);
}
return 0;
}
4. Doing Homework
很巧妙的状压dp。
#include<bits/stdc++.h>
using namespace std;
const int N=16;
struct Subject{
char name[110];
int last,d;
}a[N];
int n;
int f[1<<N],pre[1<<N];
void output(int u){
if(pre[u]!=0) output(pre[u]);
for(int i=0;i<n;i++){
if(u>>i&1^pre[u]>>i&1){
printf("%s\n",a[i].name);
break;
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%s%d%d",a[i].name,&a[i].last,&a[i].d);
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=0;i<1<<n;i++){
int sum=0;
for(int j=0;j<n;j++)
if(i>>j&1) sum+=a[j].d;
for(int j=0;j<n;j++){
if(i>>j&1) continue;
int temp=max(0,sum+a[j].d-a[j].last);
int k=i|(1<<j);
if(f[k]>f[i]+temp){
f[k]=f[i]+temp;
pre[k]=i;
}
}
}
printf("%d\n",f[(1<<n)-1]);
output((1<<n)-1);
}
return 0;
}
5. Super Jumping! Jumping! Jumping!
最长上升子序列模板题
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int a[N],f[N];
int n;
int main()
{
while(scanf("%d",&n),n){
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
f[i]=a[i];
for(int j=1;j<i;j++)
if(a[i]>a[j]) f[i]=max(f[i],f[j]+a[i]);
}
printf("%d\n",*max_element(f+1,f+1+n));
}
return 0;
}
6. Piggy-Bank
完全背包恰好装满问题。
#include<bits/stdc++.h>
using namespace std;
const int N=510,M=10010,INF=0x3f3f3f3f;
int w[N],p[N];
int f[M];
int n,m;
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
m-=n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&p[i],&w[i]);
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=w[i];j<=m;j++)
f[j]=min(f[j],f[j-w[i]]+p[i]);
if(f[m]==INF) puts("This is impossible.");
else printf("The minimum amount of money in the piggy-bank is %d.\n",f[m]);
}
return 0;
}
7. 免费馅饼
二维线性dp, f [ i ] [ j ] f[i][j] f[i][j]表示在第 i i i秒位于 j j j最多能接到的馅饼数量。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=11;
int a[N][M];
int n;
int f[N][M];
int main()
{
while(scanf("%d",&n),n){
int m=0;
memset(a,0,sizeof a);
for(int i=0;i<n;i++){
int x,t;
scanf("%d%d",&x,&t);
a[t][x]++;
m=max(m,t);
}
memset(f,-0x3f,sizeof f);
f[0][5]=0;
for(int i=1;i<=m;i++){
for(int j=0;j<=10;j++){
f[i][j]=f[i-1][j]+a[i][j];
if(j>0) f[i][j]=max(f[i][j],f[i-1][j-1]+a[i][j]);
if(j<10) f[i][j]=max(f[i][j],f[i-1][j+1]+a[i][j]);
}
}
printf("%d\n",*max_element(f[m],f[m]+11));
}
return 0;
}
8. Tickets
简单线性dp
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int a[N],b[N];
int f[N];
int n;
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++) scanf("%d",&b[i]);
f[1]=a[1];
for(int i=2;i<=n;i++) f[i]=min(f[i-1]+a[i],f[i-2]+b[i-1]);
int hh=8,mm=0,ss=0;
char str[3]="am";
ss+=f[n];
mm+=ss/60,ss%=60;
hh+=mm/60,mm%=60;
if(hh>=12) hh-=12,str[0]='p';
printf("%02d:%02d:%02d %s\n",hh,mm,ss,str);
}
return 0;
}
9. 最少拦截系统
不上升子序列覆盖整个数组的最少个数等于最长子序列的长度。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int f[N];
int n;
int main()
{
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int len=1;
f[1]=a[1];
for(int i=2;i<=n;i++){
if(a[i]>f[len]) f[++len]=a[i];
else *lower_bound(f+1,f+1+len,a[i])=a[i];
}
printf("%d\n",len);
}
return 0;
}
10. FatMouse’s Speed
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N],pre[N],path[N];
struct Mouse{
int id,w,s;
bool operator <(const Mouse &t)const{
if(w!=t.w) return w<t.w;
return s>=t.s;
}
}a[N];
void output(int u){
if(pre[u]!=u) output(pre[u]);
printf("%d\n",a[u].id);
}
int main()
{
int n=1;
while(scanf("%d%d",&a[n].w,&a[n].s)!=EOF) a[n].id=n,n++;
n--;
sort(a+1,a+1+n);
int res=0,p=0;
for(int i=1;i<=n;i++){
pre[i]=i;
f[i]=1;
for(int j=1;j<i;j++){
if(a[i].w>a[j].w&&a[i].s<a[j].s&&f[j]+1>f[i]){
f[i]=f[j]+1;
pre[i]=j;
}
}
if(f[i]>res){
p=i;
res=f[i];
}
}
printf("%d\n",res);
output(p);
return 0;
}
11. Jury Compromise
一道很麻烦的背包, f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]状态的含义是前 i i i个人种选 j j j个人差的和为 k k k的最大值。
因为记录方案需要三维,所以状态从三维降成两维感觉也没什么必要,降一维之后在memset的时候确实会快一点。
POJ的数据有点弱,有些假算法也会放过,最好到uva 323交一下。
三维状态
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=210,M=810;
int f[N][21][M],pre[N][21][M];
int a[N],b[N],sum[N],dif[N];
int n,m;
void output(int i,int j,int k){
if(j==0) return;
if(pre[i][j][k]==-1) output(i-1,j,k);
else{
output(i-1,j-1,k-dif[i]);
printf(" %d",i);
}
}
int main()
{
int T=1;
while(scanf("%d%d",&n,&m),n||m){
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
sum[i]=a[i]+b[i];
dif[i]=a[i]-b[i];
}
int offset=m*20;
memset(f,-1,sizeof f);
for(int i=0;i<=n;i++) f[i][0][offset]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=0;k<=2*offset;k++){
f[i][j][k]=f[i-1][j][k];
pre[i][j][k]=-1;
if(k-dif[i]>=0&&k-dif[i]<=2*offset&&f[i-1][j-1][k-dif[i]]!=-1){
int t=f[i-1][j-1][k-dif[i]]+sum[i];
if(f[i][j][k]<t){
pre[i][j][k]=1;
f[i][j][k]=t;
}
}
}
}
}
int p;
for(p=0;p<=offset;p++)
if(f[n][m][offset-p]!=-1||f[n][m][offset+p]!=-1)
break;
p=f[n][m][offset-p]>f[n][m][offset+p]?offset-p:offset+p;
int x=f[n][m][p];
int sum1=(x+(p-offset))/2,sum2=(x-(p-offset))/2;
printf("Jury #%d\n",T++);
printf("Best jury has value %d for prosecution and value %d for defence:\n",sum1,sum2);
output(n,m,p);
puts("\n");
}
return 0;
}
二维状态
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=210,M=810;
int f[21][M],pre[N][21][M];
int a[N],b[N],sum[N],dif[N];
int n,m;
void output(int i,int j,int k){
if(j==0) return;
if(pre[i][j][k]==-1) output(i-1,j,k);
else{
output(i-1,j-1,k-dif[i]);
printf(" %d",i);
}
}
int main()
{
int T=1;
while(scanf("%d%d",&n,&m),n||m){
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
sum[i]=a[i]+b[i];
dif[i]=a[i]-b[i];
}
int offset=m*20;
memset(f,-1,sizeof f);
f[0][offset]=0;
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
for(int k=0;k<=2*offset;k++){
pre[i][j][k]=-1;
if(k-dif[i]>=0&&k-dif[i]<=2*offset&&f[j-1][k-dif[i]]!=-1){
int t=f[j-1][k-dif[i]]+sum[i];
if(f[j][k]<t){
pre[i][j][k]=1;
f[j][k]=t;
}
}
}
}
}
int p;
for(p=0;p<=offset;p++)
if(f[m][offset-p]!=-1||f[m][offset+p]!=-1)
break;
p=f[m][offset-p]>f[m][offset+p]?offset-p:offset+p;
int x=f[m][p];
int sum1=(x+(p-offset))/2,sum2=(x-(p-offset))/2;
printf("Jury #%d\n",T++);
printf("Best jury has value %d for prosecution and value %d for defence:\n",sum1,sum2);
output(n,m,p);
puts("\n");
}
return 0;
}
12. Common Subsequence
最长公共子序列模板题
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e3+10;
char a[N],b[N];
int f[N][N];
int main()
{
while(scanf("%s%s",a+1,b+1)!=EOF){
int n=strlen(a+1);
int m=strlen(b+1);
memset(f,0,sizeof f);
int res=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
else f[i][j]=max(f[i-1][j],f[i][j-1]);
res=max(res,f[i][j]);
}
}
printf("%d\n",res);
}
return 0;
}
13. Help Jimmy
我是用从上往下dp的做法,要记录每个平台的左右端点下方是否有平台。
从下往上dp会方便一点,因为最终会汇聚到起点,可以少记录一些信息。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
struct Node{
int x1,x2,h;
bool operator <(const Node &t)const{
return h>t.h;
}
}a[N];
int l[N],r[N];
bool downl[N],downr[N];
int n,x,y,m;
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d%d%d",&n,&x,&y,&m);
for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].x1,&a[i].x2,&a[i].h);
sort(a+1,a+n+1);
memset(l,0x3f,sizeof l);
memset(r,0x3f,sizeof r);
memset(downl,0,sizeof downl);
memset(downr,0,sizeof downr);
a[0]={x,x,y};
l[0]=r[0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
if(a[j].h>a[i].h&&a[j].h-a[i].h<=m){
if(a[j].x1>=a[i].x1&&a[j].x1<=a[i].x2&&!downl[j]){
l[i]=min(l[i],l[j]+abs(a[j].x1-a[i].x1)+a[j].h-a[i].h);
r[i]=min(r[i],l[j]+abs(a[j].x1-a[i].x2)+a[j].h-a[i].h);
downl[j]=true;
}
if(a[j].x2>=a[i].x1&&a[j].x2<=a[i].x2&&!downr[j]){
l[i]=min(l[i],r[j]+abs(a[j].x2-a[i].x1)+a[j].h-a[i].h);
r[i]=min(r[i],r[j]+abs(a[j].x2-a[i].x2)+a[j].h-a[i].h);
downr[j]=true;
}
}
}
}
int res=INF;
for(int i=0;i<=n;i++){
if(a[i].h>m) continue;
if(!downl[i]) res=min(res,a[i].h+l[i]);
if(!downr[i]) res=min(res,a[i].h+r[i]);
}
printf("%d\n",res);
}
return 0;
}
14. Longest Ordered Subsequence
最长上升子序列模板题
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int a[N],f[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int len=0;
f[0]=-1;
for(int i=1;i<=n;i++){
if(a[i]>f[len]) f[++len]=a[i];
else *lower_bound(f+1,f+1+len,a[i])=a[i];
}
cout<<len<<endl;
return 0;
}
15. Treats for the Cows
简单区间dp, f [ i ] [ j ] f[i][j] f[i][j]表示选择左侧 i i i次和选择右侧 j j j次的最大值。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2010;
int f[N][N];
int a[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int len=1;len<=n;len++){
for(int l=0;l<=len;l++){
int r=len-l;
if(l) f[l][r]=max(f[l][r],f[l-1][r]+a[l]*len);
if(r) f[l][r]=max(f[l][r],f[l][r-1]+a[n-r+1]*len);
}
}
int res=0;
for(int i=0;i<=n;i++) res=max(res,f[i][n-i]);
cout<<res<<endl;
return 0;
}
16. FatMouse and Cheese
记忆化搜素,其中题意中的 k k k是指可以沿着某一方向最多走 k k k步。
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int a[N][N];
int f[N][N];
int n,m;
int nxt[4][2]={{0,1},{1,0},{-1,0},{0,-1}};
vector<int> v[N][N];
int dfs(int x,int y){
if(f[x][y]!=-1) return f[x][y];
f[x][y]=0;
for(int i=0;i<4;i++){
for(int j=1;j<=m;j++){
int tx=x+nxt[i][0]*j;
int ty=y+nxt[i][1]*j;
if(tx<1||tx>n||ty<1||ty>n) break;
if(a[tx][ty]>a[x][y]) f[x][y]=max(f[x][y],dfs(tx,ty));
}
}
return f[x][y]=f[x][y]+a[x][y];
}
int main()
{
while(scanf("%d%d",&n,&m),n!=-1&&m!=-1){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
memset(f,-1,sizeof f);
printf("%d\n",dfs(1,1));
}
return 0;
}
17. Phalanx
dp的思路很巧妙,可以用哈希表加二分将 O ( n 3 ) O(n^3) O(n3)优化到 O ( n 2 l o g n ) O(n^2logn) O(n2logn)
O ( n 3 ) O(n^3) O(n3)
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
char g[N][N];
int f[N][N];
int n;
int main()
{
while(scanf("%d",&n),n){
for(int i=1;i<=n;i++) scanf("%s",g[i]+1);
int res=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int k=0;
while(k<min(i,n-j+1)&&g[i-k][j]==g[i][j+k]){
k++;
}
if(i-1<1||j+1>n||k<=f[i-1][j+1]) f[i][j]=k;
else f[i][j]=f[i-1][j+1]+1;
res=max(res,f[i][j]);
}
}
printf("%d\n",res);
}
return 0;
}
O ( n 2 l o g n ) O(n^2logn) O(n2logn)
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int N =1010,P=131;
ULL row[N][N],col[N][N],p[N];
char g[N][N];
int f[N][N];
int n;
void init(){
p[0]=1;
for(int i=1;i<N;i++) p[i]=p[i-1]*P;
}
void init_hash(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
row[i][j]=row[i][j-1]*P+g[i][j]-'a';
for(int i=n;i>=1;i--)
for(int j=n;j>=1;j--){
if(i==n) col[i][j]=g[i][j]-'a';
else col[i][j]=col[i+1][j]*P+g[i][j]-'a';
}
}
int main()
{
init();
while(scanf("%d",&n),n){
for(int i=1;i<=n;i++) scanf("%s",g[i]+1);
init_hash();
int res=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int l=1,r=min(i,n-j+1);
while(l<r){
int mid=l+r+1>>1;
if(col[i-mid+1][j]-col[i+1][j]*p[mid]==row[i][j+mid-1]-row[i][j-1]*p[mid]) l=mid;
else r=mid-1;
}
if(i-1<1||j+1>n||l<=f[i-1][j+1]) f[i][j]=l;
else f[i][j]=f[i-1][j+1]+1;
res=max(f[i][j],res);
}
}
printf("%d\n",res);
}
return 0;
}
18. Milking Time
简单的线性dp, O ( n + m ) O(n+m) O(n+m)或者 O ( m 2 ) O(m^2) O(m2)的做法都可以。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e6+10,M=1010;
struct Node{
int st,ed,v;
bool operator<(const Node &t)const{
return ed<t.ed;
}
}a[M];
int f[N];
int n,m,k;
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a[i].st,&a[i].ed,&a[i].v);
a[i].ed+=k;
}
sort(a+1,a+1+m);
int res=0;
for(int i=0,j=1;i<=n+k;i++){
while(j<=m&&a[j].ed<=i){
if(f[a[j].st]+a[j].v>res) res=f[a[j].st]+a[j].v;
j++;
}
f[i]=res;
}
printf("%d\n",res);
return 0;
}
19. Making the Grade
因为最优解中每个数只会变成序列中的数,对 n n n个数去重之后进行 O ( n 2 ) O(n^2) O(n2)的dp就行了。
还有一种 O ( n l o g n ) O(nlogn) O(nlogn)的做法,感觉是碰到了也想不出来的存在,有兴趣的可以看这篇博客。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;
const int N=2010;
LL f[N][N];
int n,a[N];
vector<int> v;
int main()
{
scanf("%d",&n);
v.push_back(-1);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
int m=v.size()-1;
for(int i=1;i<=n;i++){
LL Min=f[i-1][1];
for(int j=1;j<=m;j++){
Min=min(Min,f[i-1][j]);
f[i][j]=Min+abs(a[i]-v[j]);
}
}
LL res=1e18;
for(int i=1;i<=m;i++) res=min(res,f[n][i]);
printf("%lld\n",res);
return 0;
}