苦练动态规划中,现在还处于玄学推转移方程的阶段,得多多学习别人的思想。
(1)巴比伦塔——UVa 437
题目链接:https://vjudge.net/contest/232313#problem/B
紫书上的dp例题,思路也很清晰易懂:一个立方体最多只有三个不同的朝上的面,所以最多也只有90种面,而只有顶面的尺寸会影响到后续的决策,所以用动态规划处理,转换成DAG上的最长路,dp[i][j]表示第i个立方体到第j个立方体的最大高度,G[i][j]表示DAG的邻接矩阵,即表示可不可以在上面,套用紫书上DAG模型的模板就行。具体细节看代码。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=95;
int n;
ll dp[maxn][maxn];//表示第i个立方体到第j个立方体的最大高度
int G[maxn][maxn];//DAG上的dp
struct Cobe
{
ll x,y,z;
Cobe(ll _x=0,ll _y=0,ll _z=0):x(_x),y(_y),z(_z){}
};
Cobe b[maxn];
//是否可以在上面
bool judge(Cobe a,Cobe b)
{
if(a.x>b.x&&a.y>b.y)
return true;
if(a.x>b.y&&a.y>b.x)
return true;
return false;
}
void handle()
{
memset(G,0,sizeof(G));
for(int i=0;i<n*3;i++)
{
for(int j=0;j<n*3;j++)
{
if(i!=j&&judge(b[i],b[j]))
G[i][j]=1;//可选状态
}
}
}
ll DP(int x1,int y1)
{
ll& ans=dp[x1][y1];
if(ans!=-1)
{
return ans;
}
for(int i=0;i<n*3;i++)
{
if(y1==i)continue;
if(G[y1][i])ans=max(ans,DP(y1,i)+b[x1].z);
}
if(ans==-1)
ans=b[x1].z+b[y1].z;//最起码能到达
return ans;
}
int main()
{
//freopen("E:\\test_data\\in.txt","r",stdin);
//freopen("E:\\test_data\\out.txt","w",stdout);
int index=1;
while(scanf("%d",&n)!=EOF)
{
if(n==0)break;
int tot=0;
ll x,y,z;
for(int i=0;i<3*n;i+=3)
{
scanf("%lld%lld%lld",&x,&y,&z);
b[i].z=z;//高
b[i].x=x;
b[i].y=y;
b[i+1].z=x;//高
b[i+1].x=y;
b[i+1].y=z;
b[i+2].z=y;//高
b[i+2].x=x;
b[i+2].y=z;
}
handle();
ll ans=0;
memset(dp,-1,sizeof(dp));
for(int i=0;i<n*3;i++)
{
for(int j=0;j<n*3;j++)
{
if(G[i][j])
ans=max(ans,DP(i,j));
}
}
printf("Case %d: maximum height = %lld\n",index++,ans);
}
return 0;
}
(2)旅行——UVa1347
题目链接:https://vjudge.net/contest/232313#problem/C
也是刘汝佳紫书上的题,将问题改成两个人同时从最左边点出发,每个人通过不同的点到达最右点的方案。dp[i][j]表示1~max(i,j)都被访问过,且两人当前的位置分别是i和j,还要走多长的距离。这样,我们再加上一个移动的条件:只允许其中一个人走到i+1,而不是i+2,i+3......这样我们就能确保每个点都访问过且不丢失最优解。状态dp[i][j]也就只能移动到dp[i+1][j]和dp[i+1,i](一个表示第一个人访问i+1,一个表示第二个人访问i+1)。具体细节看代码。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <fstream>
#include <iomanip>
#include <cmath>
using namespace std;
/*
模型转化为起始点有两个人A,B,分别走不同的点后到达最右边的点,
dp[i][j]表示1~max(i,j)都被走过之后,还需要走的最小距离,
每个点必须由A,B两个中的一个路过,这样dp公式就出来了
dp[i][j]=min(DP(i+1,j)+distance(p[i],p[i+1],DP(i+1,i)+distance(p[j],p[i+1])))
前一个表示A走过i+1这个点,后一个表示B走过i+1这个点
*/
typedef long long ll;
const double pi = acos(-1.0);
int N;
struct Point
{
int x,y;
Point(int _x=0,int _y=0):x(_x),y(_y){}
};
Point p[1005];
double dp[1005][1005];//A在i,B在j(i>=j)且i之前的所有点都走过了的情况下,还需走的最小值
double dist(Point &a,Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double DP(int i,int j)
{
if(dp[i][j]>0)
return dp[i][j];
return dp[i][j]=min(DP(i+1,j)+dist(p[i],p[i+1]),DP(i+1,i)+dist(p[j],p[i+1]));
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
int tot=0;
double x,y;
for(int i=0;i<n;i++)
{
scanf("%lf%lf",&x,&y);
p[++tot]=Point(x,y);
}
memset(dp,0.0,sizeof(dp));
for(int i=1;i<n-1;i++)
dp[n-1][i]=dist(p[n-1],p[n])+dist(p[i],p[n]);//起始状态
double ans=DP(1,1);
printf("%.2lf\n",ans);
}
return 0;
}
(3)劲歌金曲——UVa12563
题目链接:https://vjudge.net/contest/232313#problem/E
典型的01背包问题,不过注意一点:首先考虑歌曲数目最多,其次才是在这个基础上时间尽可能的长。因此,dp[i][j]表示前i首歌在j秒能唱的歌的最大数目,tim[i][j]表示前i首歌在剩余j时间内能唱的最长时间,这两个都是典型的01背包问题,直接调用模板转移就行,不过要记住之前说的先保证数量,再保证时间。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=50+5;
int A[maxn];//歌曲时间
int dp[maxn][10005];//前i个在j秒能唱的歌的最大数目
int tim[maxn][10005];//i首歌后时间为j的最大时长
//bool vis[maxn];
int main()
{
int T;
scanf("%d",&T);
int index=1;
while(T--)
{
int n,t;
scanf("%d%d",&n,&t);
for(int i=1;i<=n;i++)
{
scanf("%d",&A[i]);
}
for(int i=0;i<=t;i++)
{
dp[0][i]=0;//初始条件
tim[0][i]=0;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=t;j++)
{
dp[i][j]=(i==1?0:dp[i-1][j]);
tim[i][j]=(i==1?0:tim[i-1][j]);
if(j>A[i])
{
//先保证数量,再保证时间,所以必须先判断是否可以多一首
//再判断不能再多一首的情况下的最大时间
if(dp[i][j]<(dp[i-1][j-A[i]]+1))
{
dp[i][j]=max(dp[i][j],dp[i-1][j-A[i]]+1);
tim[i][j]=tim[i-1][j-A[i]]+A[i];
}
else if(dp[i][j]==(dp[i-1][j-A[i]]+1))
{
tim[i][j]=max(tim[i][j],tim[i-1][j-A[i]]+A[i]);
}
}
}
}
printf("Case %d: %d %d\n",index++,dp[n][t]+1,tim[n][t]+678);
}
return 0;
}
(4)牛客小白月赛7——G
题目链接:https://www.nowcoder.com/acm/contest/190/G
一个二分背包的问题,因为尽量要公平,则每个人的质量要尽可能靠近总质量/2,所以将总质量的一半作为背包容量,这就变成01背包问题了,最后少的就是wavator,多的就是小姐姐的。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
/*
将总质量的一半作为背包,就变成01背包题
*/
const int maxn=105;
int A[maxn];
int dp[maxn][10005];//表示前i个苹果装入容量为j的背包的最大质量
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
int totw=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&A[i]);
totw+=A[i];
}
int t=totw/2;
for(int i=0;i<=t;i++)
{
dp[0][i]=0;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=t;j++)
{
dp[i][j]=dp[i-1][j];
if(j>=A[i])
{
dp[i][j]=max(dp[i][j],dp[i-1][j-A[i]]+A[i]);
}
}
}
printf("%d %d\n",dp[n][t],totw-dp[n][t]);
}
return 0;
}