T1:问题 A: 火车站
题目描述
火车从始发站(称为第 1 站)开出,在始发站上车的人数为 a,然后到达第 2 站,在第 2 站有人上、下车,但上、下车的人数相同,因此在第 2 站开出时(即在到达第 3 站之前)车上的人数保持为 a 人。从第 3 站起(包括第 3 站)上、下车的人数有一定的规律:上车的人数都是前两站上车人数之和,而下车人数等于上一站上车人数,一直到终点站的前一站(第 n-1 站),都满足此规律。现给出的条件是:共有 N 个车站,始发站上车的人数为 a,最后一站下车的人数是 m(全部下车)。试问从 x 站开出时车上的人数是多少?若无解输出“No answer.”(所有数据均在 long 范围内)
输入
一行四个整数,用空格隔开,依次表示a,n,m 和 x。
输出
一行一个整数,表示从x 站开出时车上的人数。
样例输入
1 6 7 3
样例输出
2
题解
这道题是模拟?不,这是一道dp,一道很像模拟的dp。根据题意,第一站上车a人,下车0人,在线a人;假设第二站上车x人,则下车x,在线a人;
第三站:上车a+x人,下车x人,在线2a人;
第四站:上车a+2x人,下车a+x人,在线2a+x人;
第五站:上车2a+3x人,下车a+2x人,在线3a+2x人;
第六站:上车3a+5x人,下车2a+3x人,在线4a+4x人;
第七站:上车5a+8x人,下车3a+5x人,在线6a+7x人;
...........
再联系一下这道题考dp,那么这题不就很easy了吗?我们发现在线人数是规律不明显的(其实有,但是不常规,做差法也是行得通的),因此考虑上下车的人数....观察a和x分别的系数——不就是斐波那契数列吗,直接走for就行了。无解是什么情况?自然是我们发现x不是整数的情况,当我们把ans最后的a、x系数求出来之后,所有问题都迎刃而解了,额,还没完,还要记得特判特殊情况,最后让我们亲切的对该题表示WH,因为:No answer后面还有个点....
注:long范围内就是表示int类型。
参考代码
#include<cstdio>
#define LL long long
using namespace std;
LL a,n,m,x,p,dp1[1000001],dp2[1000001],s1[1000001],s2[1000001];
int main()
{
scanf("%lld%lld%lld%lld",&a,&n,&m,&x);
if(x==1||x==2)
{
printf("%lld",a);
return 0;
}
else if(x==3)
{
printf("%lld",a*2ll);
return 0;
}
dp1[1]=1;dp2[1]=0;s1[1]=1;s2[1]=0;
dp1[2]=0;dp2[2]=1;s1[2]=1;s2[2]=0;
for(int i=3;i<n;i++)
{
dp1[i]=dp1[i-2]+dp1[i-1];
dp2[i]=dp2[i-2]+dp2[i-1];
s1[i]=s1[i-1]+dp1[i-2];
s2[i]=s2[i-1]+dp2[i-2];
}
if(n==4)
{
printf("%lld",a*2ll);
return 0;
}
if(m<s1[n-1]*a)
{
printf("No answer");
return 0;
}
if((m-s1[n-1]*a)%s2[n-1]!=0)
{
printf("No answer.");
return 0;
}
else p=(m-s1[n-1]*a)/s2[n-1];
printf("%lld",s1[x]*a+s2[x]*p);
return 0;
}
T2:问题 B: 最大的子序列和
题目描述
给出一串整数a[1],a[2],a[3], ..., a[n],求出它最大的子序列和,即找出1≤i≤j≤n,使a[i]+a[i+1]+...+a[j]最大。
输入
第1行1个整数n,表示整数序列的个数。
第2行有n个整数,表示a[i],1≤i≤n。
输出
输出一行一个数,表示最大的子序列和。
样例输入
10
3 1 -6 1 7 5 -2 5 -100 10
样例输出
16
提示
【数据规模】
对于30%的数据满足:n≤102。
对于50%的数据满足:n≤104。
对于80%的数据满足:n≤106。
对于100%的数据满足:n≤107。
题解
这道题很让人舒适,因为数据范围太大,不用快读数据都还没输进去完就已经超时了。快读此处暂不做介绍。还有就是一旦用了数组,也没法跑满分,具体原因我也只能说比较玄学。现在我们来看怎样不开数组就搞定这道题。由于我们需要判断最大的连续子段和,因此我们需要就“连续”二字来分状态,连续分为一种状态,非连续又是另一种状态。什么时候我们不想连续了呢,自然是当前的前缀和已经是负数,即对答案毫无贡献,那么我们就可以将其抛弃,把当前的数作为起点更新之后的答案。但是为了预防全都是负数的情况,我们还要把初值赋为无穷小,否则输出就是0,不满足题意。
参考代码
#include<cstdio>
#include<cstring>
#include<string>
#define LL long long
using namespace std;
template <typename T>inline void in(T &x){
T ch = getchar(), xx = 1; x = 0;
while(!isdigit(ch)) xx = ch == '-' ? -1 : xx, ch = getchar();
while(isdigit(ch)) x = (x<<3) + (x<<1) + ch - '0', ch = getchar();
x *= xx;
}//快读部分
LL dp[2],max1=-99999999999999999ll;
int n;
int main()
{
scanf("%d",&n);LL k;
memset(dp,-127,sizeof(dp));//极小值
for(int i=1;i<=n;i++)
{
in(k);
dp[0]=k;
dp[1]+=k;
max1=max1>dp[0]?max1:dp[0];
max1=max1>dp[1]?max1:dp[1];
if(dp[0]>dp[1]) dp[1]=dp[0];
}
printf("%lld",max1);
return 0;
}
T3:问题 C: 合唱队形
题目描述
N位同学站成一排,音乐 老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形 :设K位同学从左到右依次编号为1,2, ..., K,他们的身高分别为T1,T2, ..., TK, 则他们的身高满足T1<T2< ... <Ti,且Ti>Ti+1> ... >TK(1≤i≤K)。
本题的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入
第1行是1个正整数N,表示同学的总数。
第2行有n个正整数,之间用一个空格分隔,第i个整数Ti(1≤i≤K)是第i位同学的身高(厘米)。
输出
一行一个整数,表示最少需要几位同学出列。
样例输入
8
186 186 150 200 160 130 197 220
样例输出
4
提示
【数据规模】
对于50%的数据满足:n≤20。
对于100%的数据满足:n≤1000。
题解
这道题我们需要转化成dp。题目要求我们计算最少有几个同学出列,就是找最多有多少人能够参与合唱队形。我们可以看见这是一个金字塔形状的队形,因此我们完全可以将问题转化为:顺序和倒序分别找出最长不下降子序列的长度,然后对于任意一点,我们可以找出这个人前面的最长不下降队列长度和后面的最长不下降队列长度,将两个相加再加一就是一种以当前这人为金字塔尖的一种队形。然后在处理出所有的队形后,O(n)扫一遍就能够得到正确答案了。
参考代码
#include<cstdio>
using namespace std;
int n,a[1001],dp1[1001],dp2[1001],ans=-999999999;
int max1(int p,int q) { return p>q?p:q; }
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
dp1[1]=1;
for(int i=2;i<=n;i++)
{
dp1[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
dp1[i]=max1(dp1[i],dp1[j]+1);
}
}
dp2[n]=1;
for(int i=n-1;i>=1;i--)
{
dp2[i]=1;
for(int j=n;j>i;j--)
{
if(a[j]<a[i])
dp2[i]=max1(dp2[i],dp2[j]+1);
}
}
for(int i=1;i<=n;i++)
{
if(dp1[i]+dp2[i]>ans)
ans=dp1[i]+dp2[i];
}
printf("%d",n-ans+1);
return 0;
}
T4:问题 D: 数字组合
题目描述
给定N个正整数A1,A2,…,ANA1,A2,…,AN,从中选出若干个数,使它们的和为M,求有多少种选择方案。
输入
第一行包含两个整数N和M。
第二行包含N个整数,表示A1,A2,…,AN
输出
包含一个整数,表示M。
样例输入
4 4
1 1 2 2
样例输出
3
提示
1≤N≤100
1≤M≤10000
1≤Ai≤1000
题解
原本这道题可以搜索做,再不济就是对半深搜优化一下,但是n最多可以取100,就没法搜了。这个时候我们发现ai很小,才1000,这样n*ai也很小,就考虑能不能用背包来解决这道题。最外面我们枚举是第几个数,在内层我们枚举每一个可能存在的和,看一下能否转移到下一个状态。为了不冲突,可以倒着枚举也可以往前推。用这种类似于桶的思路,我们可以很容易搞定最后所求和为m的个数。注意,初值是vis[0]=1,否则只会输出0。
参考代码
#include<cstdio>
using namespace std;
int n,m,vis[10001];
int main()
{
scanf("%d%d",&n,&m);
vis[0]=1;
for(int i=1;i<=n;i++)
{
int k;scanf("%d",&k);
for(int p=m;p>=k;p--)
vis[p]+=vis[p-k];
}
printf("%d",vis[m]);
return 0;
}
T5:问题 E: 乌龟棋
题目描述
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘只有一行,该行有 N 个格子,每个格子上一个分数(非负整数)。
棋盘第 1 格是唯一的起点,第 N 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中共有 M 张爬行卡片,分成 4 种不同的类型(M 张卡片中不一定包含所有 4 种类型的卡片),每种类型的卡片上分别标有1、2、3、4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。
游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。
玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入
输入文件的每行中两个数之间用一个空格隔开。
第 1 行 2 个正整数 N 和 M,分别表示棋盘格子数和爬行卡片数。
第 2 行 N 个非负整数,a1,a2,……,aNa1,a2,……,aN,其中 aiai 表示棋盘第 i 个格子上的分数。
第 3 行 M 个整数,b1,b2,……,bMb1,b2,……,bM,表示 M 张爬行卡片上的数字。
输入数据保证到达终点时刚好用光 M 张爬行卡片。
输出
输出只有 1 行,包含 1 个整数,表示小明最多能得到的分数。
样例输入
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
样例输出
73
提示
1≤N≤3501≤N≤350,
1≤M≤120
0≤ai≤1000
1≤bi≤41
每种爬行卡片的张数不会超过40。
题解
又是一道板题,只是卡片变多了。看到卡片每种不超过40,并且有4种卡片,直接考虑4维dp,每一维存一种类型的卡片,dp[i][j][k][p]表示用了对应的卡片后所得的最高得分(过程未知)。现在我们来看状态转移,直接就考虑像后面转移,每一次转移都会使后面的那个dp=当前dp+w,w表示走了那么多步能多得的分数。现在再来看for里面该怎么写,最外层我选择枚举当前位置,后面跟着3个for分别枚举1卡、2卡、3卡的数量,最后再减出4卡的数量(如果不是整数,就舍弃),每种卡片从0开始枚举(存在不选的情况),再用vis存每种卡最多放几张,如果大于vis,也continue。最后我们求的就是:dp[vis[1]][vis[2]][vis[3]][vis[4]]+a[0]。(初始分数是它送你的,不用谢~)
参考代码
#include<cstdio>
using namespace std;
int n,m,a[500],vis[10],dp[41][41][41][41],sum=0;
int max1(int p,int q) { return p>q?p:q; }
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
int p;scanf("%d",&p);
vis[p]++;sum+=p;
}
dp[1][0][0][0]=a[1];
dp[0][1][0][0]=a[2];
dp[0][0][1][0]=a[3];
dp[0][0][0][1]=a[4];
for(int i=1;i<=sum;i++)
{
for(int j=0;j<=i&&j<=vis[1];j++)
{
for(int k=0;k*2+j<=i&&k<=vis[2];k++)
{
for(int z=0;z*3+k*2+j<=i&&z<=vis[3];z++)
{
int w=(i-j-k*2-z*3)/4;
if(w*4!=(i-j-k*2-z*3)) continue;
if(w*4+z*3+k*2+j>i) continue;
if(w>vis[4]) continue;
dp[j+1][k][z][w]=max1(dp[j+1][k][z][w],dp[j][k][z][w]+a[i+1]);
dp[j][k+1][z][w]=max1(dp[j][k+1][z][w],dp[j][k][z][w]+a[i+2]);
dp[j][k][z+1][w]=max1(dp[j][k][z+1][w],dp[j][k][z][w]+a[i+3]);
dp[j][k][z][w+1]=max1(dp[j][k][z][w+1],dp[j][k][z][w]+a[i+4]);
}
}
}
}
printf("%d",dp[vis[1]][vis[2]][vis[3]][vis[4]]+a[0]);
return 0;
}
T6:问题 F: 低买
题目描述
给定一段时间内股票的每日售价(正16位整数)。
你可以选择在任何一天购买股票。
每次你选择购买时,当前的股票价格必须严格低于你之前购买股票时的价格。
编写一个程序,确定你应该在哪些天购进股票,可以使得你能够购买股票的次数最大化。
例如,下面是一个股票价格时间表:
Day 1 2 3 4 5 6 7 8 9 10 11 12 Price 68 69 54 64 68 64 70 67 78 62 98 87
如果每次购买都必须遵循当前股票价格严格低于之前购买股票时的价格,那么投资者最多可以购买四次该股票。
买进方案之一为:
Day 2 5 6 10 Price 69 68 64 62
输入
第1行包含整数 N,表示给出的股票价格的天数。
第2至最后一行,共包含 N 个整数,每行10个,最后一行可能不够10个,表示 N 天的股票价格。
同一行数之间用空格隔开。
输出
输出占一行,包含两个整数,分别表示最大买进股票次数以及可以达到最大买进次数的方案数。
如果两种方案的买入日序列不同,但是价格序列相同,则认为这是相同的方案(只计算一次)。
样例输入
输入样例1:
12
68 69 54 64 68 64 70 67 78 62
98 87
输入样例2:
5
4 3 2 1 1
样例输出
输出样例1:
4 2
输出样例2:
4 1
提示
1≤N≤5000
题解
jjp这题的第一问很简单,和上面某一道题的解法几乎一模一样。这里就不再赘述(也可以再啰嗦一下,第一问就是求严格最长不上升子序列)。现在我们需要判断第二问哪些价格序列是相同的。如果说我们发现两个数相同并且两个数的dp值也相同,是否意味着这两个数的转移过程是一样的。由于我们从小到大枚举,因此答案很显然是对的,这样我们就应该让一个数的size变为0,不影响后面的答案。
否则,当我们确定这个数是由某一个数转移过来的,就累加vis的值,表示转移个数也可以接着转移。而当dp值为1时,这个数本身就可能时答案,因此vis要先赋成1,再进行下面的操作。
参考代码
#include<cstdio>
using namespace std;
int n,a[5001],dp[5001],vis[5001];
int max1(int p,int q) { return p>q?p:q; }
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
dp[i]=1;
for(int j=1;j<i;j++)
if(a[j]>a[i])
dp[i]=max1(dp[i],dp[j]+1);
}
int maxn1=-999999999,maxn2=0;
for(int i=1;i<=n;i++)
maxn1=max1(maxn1,dp[i]);
for(int i=1;i<=n;i++)
{
if(dp[i]==1) vis[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]>a[i]&&dp[i]==dp[j]+1) vis[i]+=vis[j];
if(a[j]==a[i]&&dp[i]==dp[j])
vis[i]=0;
}
}
for(int i=1;i<=n;i++)
if(dp[i]==maxn1)
maxn2+=vis[i];
printf("%d %d",maxn1,maxn2);
return 0;
}
T7:问题 G: 炮兵阵地
题目描述
司令部的将军们打算在 N×M 的网格地图上部署他们的炮兵部队。一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地(用 H 表示),也可能是平原(用 P 表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入
第一行包含两个由空格分割开的正整数,分别表示 N 和 M;
接下来的 N 行,每一行含有连续的 M 个字符(P 或者 H),中间没有空格。按顺序表示地图中每一行的数据。
输出
仅一行,包含一个整数 K,表示最多能摆放的炮兵部队的数量。
样例输入
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
样例输出
6
提示
【数据范围与提示】
N≤100,M≤10。
题解
这道题让我想起了一道经典的二进制压缩的题目,只不过那道题只和上一行有关,而这道题却和上两行有关。首先把‘P’、‘H’压缩成0,1,然后我们再枚举上下三行的情况,如果一堆特判条件都能满足,那么就转移答案。特判条件就有点多了,首先每一行先看自身能否满足,判断条件就是当前行x&(x<<1)是否是0,如果是0就说明满足不相邻的条件,对于(x<<2)也是这样。我们再来看对于枚举的二进制,是否符合地图条件。在这里唯一需要考虑的就是地图的平地H(也就是1)不能种炮兵,就是说两个数与起来一定要为0,否则也不满足条件,最后我们用for循环实现这样一个dp,就能够将答案计算出来。注意这道题的内存限制,需要开滚动数组,即第三维是3。
参考代码
#include<cstdio>
using namespace std;
int n,m,sum[1030];char s[1001];
int e_map[1001],num[1001],init[1030][101];
int max1(int p,int q) { return p>q?p:q; }
int dp[1030][1030][3],ans=-999999999;
int getsum(int k)
{
int ens=0;
while(k)
{
ens++;
k-=(k&-k);
}
return ens;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%s",s);
for(int j=0;j<m;j++)
if(s[j]=='H')
e_map[i]|=(1<<(m-j-1));
for(int j=0;j<(1<<m);j++)
if(((j&e_map[i])==0)&&((j&(j<<1))==0)&&((j&(j<<2))==0))
init[++num[i]][i]=j;//存单层满足条件的二进制
}
for(int i=0;i<(1<<m);i++)
sum[i]=getsum(i);//处理1的个数
for(int i=1;i<=num[1];i++)
dp[0][init[i][1]][0]=sum[init[i][1]];//第一层
for(int i=1;i<=num[1];i++)
{
for(int j=1;j<=num[2];j++)
{
int s1=init[i][1],s2=init[j][2];
if(s1&s2) continue;
dp[s1][s2][1]=sum[s1]+sum[s2];//第二层
}
}
for(int i=1;i<n-1;i++)//由第i层、i+1层转移到i+2层
{
for(int p=1;p<=num[i];p++)//第i层
{
for(int q=1;q<=num[i+1];q++)//第i+1层
{
int s1=init[p][i],s2=init[q][i+1];
if(s1&s2) continue;//判断上下两层
for(int r=1;r<=num[i+2];r++)//第i+2层
{
int s3=init[r][i+2];
if((s3&s2)||(s3&s1)) continue;//判断上下三层
dp[s2][s3][(i+1)%3]=max1(dp[s2][s3][(i+1)%3],
sum[s3]+dp[s1][s2][i%3]);//更新答案
}
}
}
}
for(int i=1;i<=num[n-1];i++)//答案必定在最后两层
for(int j=1;j<=num[n];j++)
{
int s1=init[i][n-1],s2=init[j][n];
if(s1&s2) continue;
ans=max1(ans,dp[s1][s2][(n-1)%3]);
}
printf("%d",ans);
return 0;
}
T8:问题 H: 数字金字塔
题目描述
观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。
输入
第一行包含R(1<= R<=1000),表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有的被供应的整数是非负的且不大于100。
输出
单独的一行,包含那个可能得到的最大的和。
样例输入
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
样例输出
max=86
题解
经典的dp题。作为我的第一道dp题,我准备认真的讲讲这道题是怎么做的。首先我们来看答案是由什么组成的,一定是在最下面一层中的一个点走出来的。那么最下面的点的答案又和什么有关呢,自然和它直接相连的左上和右上的点转移过来,再往上一直推上去,等到了边界也就是a[1][1]时,就return,然后依次传值到最下面,这就是这道题的递归做法。我为什么讲这个呢?是因为把它反一转,变成递推的形式,就是dp了。Dp讲究状态,从上一层到下一层就是状态的转移,决策自然是取最大值,而每个点可以分为2个阶段,左上和右上,这也就构成了动态规划的经典元素:状态、阶段、决策。总的来说,这是道经典题,还是很有意义的。水过去
参考代码
#include<cstdio>
using namespace std;
int n,a[1001][1001],dp[1001][1001],maxn=-999999999;
int max1(int p,int q) { return p>q?p:q; }
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&a[i][j]);
dp[1][1]=a[1][1];
for(int i=2;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
dp[i][j]=max1(dp[i-1][j],dp[i-1][j-1])+a[i][j];
}
}
for(int i=1;i<=n;i++)
maxn=max1(maxn,dp[n][i]);
printf("max=%d",maxn);
return 0;
}