001.Max Sum Plus Plus
题目链接 https://vjudge.net/problem/HDU-1024
(dp[i][j])为前j个数将其j为其中一组,分为i组每组相加的和。
这状态转移方程为(:dp[i][j]=max(dp[i][j-1]+a[i],max(dp[i-1][k])+a[i]))
其中(k<=j-1),
复杂度为(O(mn^{2})),显然算法超时。
我们可以对(max(dp[i-1][k]))把这层优化掉,即用Max[j]来前j个数分成i组最大的和。
最后状态转移方程为(:d[j]=max(dp[j-1]+a[i]+Max[j-1]+a[i]))。
代码如下(:)
#include <iostream>
using namespace std;
typedef long long ll;
int m,n;
ll a[1000007],dp[1000007],Mx[1000007],Max[1000007];
int main(){
while(scanf("%d %d",&m,&n)!=EOF){
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int i=0;i<=n;i++){
dp[i]=Max[i]=0;
Mx[i]=-1e18;
}
for(int i=1;i<=m;i++){
for(int j=i;j<=n;j++){
dp[j]=max(dp[j-1]+a[j],Max[j-1]+a[j]);
Mx[j]=max(dp[j],Mx[j-1]);
}
for(int j=i;j<=n;j++){
Max[j]=Mx[j];
Mx[j]=-1e18;
}
}
printf("%lld
",Max[n]);
}
}
002.MonkeyandBanana
题目链接:https://vjudge.net/problem/HDU-1069#author=prayerhgq
解法一:(dfs+记忆化)
枚举每个箱子的六种放法能否放在之前的箱子上,即这个箱子被之前的箱子长宽都小,直到不能放箱子即返回高度。
直接枚举超时,加上记忆化搜索即可。
#include <iostream>
#include <cstring>
using namespace std;
struct node{
int l,w,h;
}pre[100];
int n,tot,dp[100][10];
int dfs(int p,int s){
if(dp[p][s]!=-1) return dp[p][s];
int ans=0,l,w;
if(s==1){
l=pre[p].l,w=pre[p].w;
}else if(s==2){
l=pre[p].w,w=pre[p].l;
}else if(s==3){
l=pre[p].l,w=pre[p].h;
}else if(s==4){
l=pre[p].h,w=pre[p].l;
}else if(s==5){
l=pre[p].w,w=pre[p].h;
}else if(s==6){
l=pre[p].h,w=pre[p].w;
}
for(int i=1;i<=n;i++){
if(pre[i].l<l&&pre[i].w<w){
ans=max(ans,dfs(i,1)+pre[i].h);
}
if(pre[i].w<l&&pre[i].l<w){
ans=max(ans,dfs(i,2)+pre[i].h);
}
if(pre[i].l<l&&pre[i].h<w){
ans=max(ans,dfs(i,3)+pre[i].w);
}
if(pre[i].h<l&&pre[i].l<w){
ans=max(ans,dfs(i,4)+pre[i].w);
}
if(pre[i].w<l&&pre[i].h<w){
ans=max(ans,dfs(i,5)+pre[i].l);
}
if(pre[i].h<l&&pre[i].w<w){
ans=max(ans,dfs(i,6)+pre[i].l);
}
}
return dp[p][s]=ans;
}
int main(){
while(scanf("%d",&n)&&n!=0){
int ml=0;
for(int i=1;i<=n;i++){
scanf("%d %d %d",&pre[i].l,&pre[i].w,&pre[i].h);
ml=max(ml,max(pre[i].h,max(pre[i].w,pre[i].l)));
}
memset(dp,-1,sizeof(dp));
int res=0;
for(int i=1;i<=n;i++){
res=max(res,dfs(i,1)+pre[i].h);
res=max(res,dfs(i,2)+pre[i].h);
res=max(res,dfs(i,3)+pre[i].w);
res=max(res,dfs(i,4)+pre[i].w);
res=max(res,dfs(i,5)+pre[i].l);
res=max(res,dfs(i,6)+pre[i].l);
}
printf("Case %d: maximum height = %d
",++tot,res);
}
}
解法二 dp
由题意易得,每个箱子的六个形态最多用一次,这就相当于有(6*n)个箱子
前个箱子的长宽要大于后个箱子,那么就先对每个箱子按长度由大到小排序
这样我们能推出状态转移方程为(:d[i]=max(d[i],d[j]+pre[i].h))
其中d[i]为选前i个箱子堆成的高度,其中i箱子在最上面,(pre[i].h)为第i个箱子的高度
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,cnt,dp[1000];
struct node{
int l,w,h;
}pre[1000];
bool cmp(node x,node y){
return x.l>y.l;
}
int main(){
while(scanf("%d",&n)&&n!=0){
int tot=0;
for(int i=1;i<=n;i++){
int l,w,h;
scanf("%d %d %d",&l,&w,&h);
pre[++tot].l=l,pre[tot].w=w,pre[tot].h=h;
pre[++tot].l=w,pre[tot].w=l,pre[tot].h=h;
pre[++tot].l=l,pre[tot].w=h,pre[tot].h=w;
pre[++tot].l=h,pre[tot].w=l,pre[tot].h=w;
pre[++tot].l=h,pre[tot].w=w,pre[tot].h=l;
pre[++tot].l=w,pre[tot].w=h,pre[tot].h=l;
}
memset(dp,0,sizeof(dp));
sort(pre+1,pre+1+tot,cmp);
int ans=0;
for(int i=1;i<=tot;i++){
dp[i]=pre[i].h;
for(int j=1;j<=i-1;j++){
if(pre[j].w>pre[i].w&&pre[j].l>pre[i].l)
dp[i]=max(dp[i],dp[j]+pre[i].h);
}
ans=max(ans,dp[i]);
}
printf("Case %d: maximum height = %d
",++cnt,ans);
}
}
003.Super Jumping! Jumping! Jumping!
LIS变形,状态转移方程 dp[i]为前i个数以第i个数结尾的上升子序列的和。
状态转移方程为:(dp[i]=max(dp[i],dp[j]+a[i]))。
最后取(ans=max(dp[i]))。
#include <iostream>
using namespace std;
typedef long long ll;
int n;
ll a[1007],dp[1007];
int main(){
while(scanf("%d",&n)&&n!=0){
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
ll ans=0;
for(int i=1;i<=n;i++){
dp[i]=a[i];
for(int j=1;j<=i-1;j++){
if(a[j]<a[i]){
dp[i]=max(dp[i],dp[j]+a[i]);
}
}
ans=max(ans,dp[i]);
}
printf("%lld
",ans);
}
}
004.Piggy-Bank
完全背包问题变形。需注意dp[i]初始化为(1e9),(dp[0]=0)。
状态转移方程为:(dp[j]=max(dp[j],dp[j-v[i]]+w[i]))
其中(jge v[i])且(dp[j-v[i]] e1e9)。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n,a[1000007],dp[1000007],cnt;
int main(){
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
dp[i]=0;
}
cnt=0;
for(int i=1;i<=n;i++){
int inx=lower_bound(dp,dp+cnt,a[i])-dp;
dp[inx]=a[i];
if(inx==cnt){
cnt++;
}
}
printf("%d
",cnt);
}
}
005.免费馅饼
暴搜,每秒能有三种状态,在当期坐标(p),在(p-1)坐标,在(p+1)坐标。
暴搜超时,最后加上记忆化即可。
#include <iostream>
#include <cstring>
using namespace std;
int n,mx,dp[20][100007],tem[20][100007];
int dfs(int p,int tim){
if(tim>mx){
return 0;
}
if(dp[p][tim]!=-1) return dp[p][tim];
int ans=0;
if(p+1<=10) ans=max(ans,dfs(p+1,tim+1)+tem[p][tim]);
if(p-1>=0) ans=max(ans,dfs(p-1,tim+1)+tem[p][tim]);
ans=max(ans,dfs(p,tim+1)+tem[p][tim]);
return dp[p][tim]=ans;
}
int main(){
while(scanf("%d",&n)&&n!=0){
mx=0;
memset(tem,0,sizeof(tem));
memset(dp,-1,sizeof(dp));
for(int i=1;i<=n;i++){
int t,x;
scanf("%d %d",&x,&t);
if(t>mx) mx=t;
tem[x][t]++;
}
printf("%d
",dfs(5,0));
}
}
006.Tickets
一维递推。
状态转移方程(dp[i]=max(dp[i-1]+a[i],dp[i-2]+b[i]))
或者记忆化搜索
#include <iostream>
#include <cstring>
using namespace std;
int n,k,s[2007],d[2007],dp[2007];
int dfs(int p){
if(p>k){
return 0;
}
if(dp[p]!=-1) return dp[p];
int ans=1e9;
ans=min(ans,dfs(p+1)+s[p]);
if(p!=k) ans=min(ans,dfs(p+2)+d[p]);
return dp[p]=ans;
}
int main(){
scanf("%d",&n);
while(n--){
scanf("%d",&k);
memset(dp,-1,sizeof(dp));
for(int i=1;i<=k;i++){
scanf("%d",&s[i]);
}
for(int i=1;i<=k-1;i++){
scanf("%d",&d[i]);
}
int t=8*3600+dfs(1);
int go=t/(3600*12);
int h=(t/3600)%12;
int m=(t/60)%60;
int s=t%60;
if(go%2==0){
printf("%02d:%02d:%02d am
",h,m,s);
}else{
printf("%02d:%02d:%02d pm
",h,m,s);
}
}
}
007.最少拦截系统
题意是要找最少条非严格下降子序列,可转换成找一条严格上升子序列。
复杂度(o(n^2))的就不赘述了。
下面说说(o(nlogn))的做法。
首先定义一下dp[i]为长度为i的上升子序列第i个数的值
如果想要一个上升子序列尽可能长,那么dp[i]的数尽可能小,那么他后面能接的数也会尽可能的多。
例如一个序列:389 207 155 300 299 170 158 65。
我们要找它的最长上升子序列。首先在dp数组上找大于或等于389的数,将他插入dp数组。
(dp={389})
找大于或等于207的数,我们发现207能代替389。
(dp={207})
找大于或等于155的数。
(dp={155})
找大于或等于300的数。
(dp={155,300})
找大于或等于299的数。
(dp={155,299})
找大于或等于170的数。
(dp={155,170})
找大于或等于158的数。
(dp={155,158})
找大于或等于207的数
(dp={155,207})
所以最长上升子序列的长度为2。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n,a[1000007],dp[1000007],cnt;
int main(){
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
dp[i]=0;
}
cnt=0;
for(int i=1;i<=n;i++){
int inx=lower_bound(dp,dp+cnt,a[i])-dp;
dp[inx]=a[i];
if(inx==cnt){
cnt++;
}
}
printf("%d
",cnt);
}
}
008.Jury Compromise
(dp[i][j][k])为前i个人选j个,(k=a[i]-b[i]),(dp[i][j][k])的值为前i个(a[i])和(b[i])的和。
有选或不选两个状态转移。
选的话(dp[i][j][k]=dp[i-1][j-1][k+(a[i]-b[i])])((k+(a[i]-b[i])可能为负数,加个(base)偏移量))。
不选(dp[i][j][k]=dp[i-1][j][k])。
当k相同时选前i个a[i]和b[i]最大的
那么状态转移方程为(:dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-1][k+(a[i]-b[i])]))。
当(dp[i-1][j][k]!=dp[i][j][k]),代表选择了第i个人,统计(suma)和(sumb),和第(i)个人
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f;
const int base=400;
int n,m,tot,a[207],b[207],x[27][207],y[27][207];
int dp[207][27][807];
vector <int> ans;
int main(){
while(scanf("%d %d",&n,&m)!=EOF){
if(n==0&&m==0) break;
for(int i=1;i<=n;i++){
scanf("%d %d",&a[i],&b[i]);
}
memset(dp,-inf,sizeof(dp));
ans.clear();
dp[0][0][base]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=800;k++){
dp[i][j][k]=dp[i-1][j][k];
int t=k+(a[i]-b[i]);
if(t<0||t>=800) continue;
if(j<1) continue;
if(k+a[i]-b[i]>=0)
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][t]+a[i]+b[i]);
}
}
}
int v=0;
while(dp[n][m][base+v]<0&&dp[n][m][base-v]<0) v++;
if (dp[n][m][base-v]>dp[n][m][base+v]){
v=base-v;
}else{
v=base+v;
}
int i=n,j=m,suma=0,sumb=0;
while(j){
if(dp[i][j][v]==dp[i-1][j][v]){
i--;
}else{
suma+=a[i];
sumb+=b[i];
ans.push_back(i);
v=v+(a[i]-b[i]);
i--,j--;
}
}
sort(ans.begin(),ans.end());
printf("Jury #%d
",++tot);
printf("Best jury has value %d for prosecution and value %d for defence:
",suma,sumb);
for(int i=0;i<ans.size();i++){
printf("%d ",ans[i]);
}
printf("
");
}
}
009.Common Subsequence
最长公共子序列板子题。
(dp[i][j])为(s)串的前(i)的子序列和(t)串前(j)的子序列的最长公共子序列。
当(s[i]==t[j])时。
(dp[i][j]=dp[i-1][j-1]+1)。
当(s[i]!=t[j])时。
(dp[i][j]=max(dp[i-1][j],dp[i][j-1]))。
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,dp[1000][1000];
char s1[1000],s2[1000];
int main(){
while(cin>>(s1+1)>>(s2+1)){
n=strlen(s1+1);
m=strlen(s2+1);
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s1[i]==s2[j]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
printf("%d
",dp[n][m]);
}
}
010.Help Jimmy
离散+记忆化搜索。
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
int T,N,X,Y,MAX,dp[1007][2007];
struct node{
int x,y,h;
}pre[1007];
vector <int> ho;
bool cmp(node x,node y){
return x.h>y.h;
}
int get(int x){
return lower_bound(ho.begin(),ho.end(),x)-ho.begin();
}
int dfs(int pos,int s){
int ans=1e9,ok1=1,ok2=1;
//cout<<"pos="<<pos<<" s="<<s<<endl;
if(pos==N){
return 0;
}
if(dp[pos][s]!=-1) return dp[pos][s];
for(int i=1;i<=N;i++){
if(pre[i].h>=pre[pos].h) continue;
int l=pre[pos].x,r=pre[pos].y;
if(ok1==1&&l>=pre[i].x&&l<=pre[i].y){
ok1=0;
if(abs(pre[pos].h-pre[i].h)<=MAX)
ans=min(ans,dfs(i,l)+abs(ho[s]-ho[l])+abs(pre[pos].h-pre[i].h));
}
if(ok2==1&&r>=pre[i].x&&r<=pre[i].y){
ok2=0;
if(abs(pre[pos].h-pre[i].h)<=MAX)
ans=min(ans,dfs(i,r)+abs(ho[r]-ho[s])+abs(pre[pos].h-pre[i].h));
}
if(ok1==0&&ok2==0) break;
}
return dp[pos][s]=ans;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d %d %d %d",&N,&X,&Y,&MAX);
ho.clear();
for(int i=1;i<=N;i++){
scanf("%d %d %d",&pre[i].x,&pre[i].y,&pre[i].h);
ho.push_back(pre[i].x);
ho.push_back(pre[i].y);
}
pre[N+1].x=-20000,pre[N+1].y=20000,pre[N+1].h=0;
ho.push_back(pre[N+1].x),ho.push_back(pre[N+1].y);
ho.push_back(X);
N=N+1;
ho.erase(unique(ho.begin(),ho.end()),ho.end());
sort(ho.begin(),ho.end());
for(int i=1;i<=N;i++){
pre[i].x=lower_bound(ho.begin(),ho.end(),pre[i].x)-ho.begin();
pre[i].y=lower_bound(ho.begin(),ho.end(),pre[i].y)-ho.begin();
}
memset(dp,-1,sizeof(dp));
sort(pre+1,pre+1+N,cmp);
int res=0;
for(int i=1;i<=N;i++){
if(get(X)>=pre[i].x&&get(X)<=pre[i].y){
res=dfs(i,get(X))+abs(Y-pre[i].h);
break;
}
}
printf("%d
",res);
}
}
011.Longest Ordered Subsequence
最长上升子序列,没什么好讲的
#include <iostream>
using namespace std;
int n,a[1007],d[1007];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int res=0;
for(int i=1;i<=n;i++){
d[i]=1;
for(int j=1;j<=i-1;j++){
if(a[j]<a[i])
d[i]=max(d[i],d[j]+1);
}
res=max(res,d[i]);
}
printf("%d
",res);
}
012.Treats for the Cows
区间dp或者记忆化搜索
解法一:
#include <iostream>
using namespace std;
typedef long long ll;
int n;
ll a[2007],dp[2007][2007];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int len=n-2;len>=0;len--){
for(int i=1;i+len<=n;i++){
int j=i+len;
ll res1=0,res2=0;
if(j+1<=n) res1=dp[i][j+1]+(n-len-1)*a[j+1];
if(i-1>=1) res2=dp[i-1][j]+(n-len-1)*a[i-1];
dp[i][j]=max(res1,res2);
//printf("dp[%d][%d]=%d ",i,j,dp[i][j]);
}
}
ll ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,dp[i][i]+n*a[i]);
}
printf("%lld
",ans);
}
解法二:
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
int n;
ll a[2007],dp[2007][2007];
ll dfs(int x,int y){
if(y<x){
return 0;
}
if(dp[x][y]!=-1) return dp[x][y];
ll ans=0;
ans=max(dfs(x,y-1)+(n-abs(y-x))*a[y],dfs(x+1,y)+(n-abs(y-x))*a[x]);
return dp[x][y]=ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
memset(dp,-1,sizeof(dp));
printf("%lld
",dfs(1,n));
}
013.FatMouse and Cheese
二维最长上升子序列
#include <iostream>
#include <cstring>
#define judge(x,y) x>=1&&x<=n&&y>=1&&y<=n
using namespace std;
int n,m,dp[107][107],a[107][107];
int dx[]={1,-1,0,0};
int dy[]={0,0,-1,1};
int dfs(int x,int y){
if(dp[x][y]!=-1) return dp[x][y];
int ans=a[x][y];
for(int i=0;i<4;i++){
for(int j=1;j<=m;j++){
int nx=x+j*dx[i],ny=y+j*dy[i];
if(judge(nx,ny)&&a[x][y]<a[nx][ny]){
ans=max(ans,dfs(nx,ny)+a[x][y]);
}
}
}
return dp[x][y]=ans;
}
int main(){
while(scanf("%d %d",&n,&m)!=EOF){
if(n==-1&&m==-1) break;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
}
}
memset(dp,-1,sizeof(dp));
printf("%d
",dfs(1,1));
}
}