洛谷 P2530 [SHOI2001]化工厂装箱员
题目描述
��118号工厂是世界唯一秘密提炼锎的化工厂,由于提炼锎的难度非常高,技术不是十分完善,所以工厂生产的锎成品可能会有3种不同的纯度,A:100%,B:1%,C:0.01%,为了出售方便,必须把不同纯度的成品分开装箱,装箱员grant第1次顺序从流水线上取10个成品(如果一共不足10个,则全部取出),以后每一次把手中某种纯度的成品放进相应的箱子,然后再从流水线上顺序取一些成品,使手中保持10个成品(如果把剩下的全部取出不足10个,则全部取出),如果所有的成品都装进了箱子,那么grant的任务就完成了。
��由于装箱是件非常累的事情,grant希望他能够以最少的装箱次数来完成他的任务,现在他请你编个程序帮助他。
输入格式
��第1行为n(1<=n<=100),为成品的数量
��以后n行,每行为一个大写字母A,B或C,表示成品的纯度。
输出格式
��仅一行,为grant需要的最少的装箱次数。
输入输出样例
输入 #1复制
输出 #1复制
题解:
一道最优化题,而且有状态有转移,我们考虑DP。
特意请教了大佬@JZYShuraK,什么时候我们考虑DP呢?首先是DP计数(数数题恶心得要命)然后是最优化题,尤其是,那种不需要了解过程,只需要输出结果的(比如本题,我们压根不需要知道到底什么时候拿走了什么、多少物品,只需要知道最少拿多少次(即我们要统计的答案))。这个时候可以果断选择DP。
注意到这道题的数据范围:N是1-100的。这个数据范围甚至可以支持我们跑(O(n^4))的算法,无论是时间复杂度还是空间复杂度都是如此。这道题一开始把我难住的一个点是状态的设置。总觉得怎么设置都很令人不满意。后来终于恍然:一维两维搞不清楚状态,多开几维不就成了?反正空间够用。
那么,我们的状态便设置成:
(dp[i][j][k][m])表示一共(i)个物品,手中还剩(j)个(A),(k)个(B),(m)个(C)时的最小装箱次数。
然后我们考虑初值和答案:因为要求最小值,所以答案显然设置成最大值。特殊地,不难发现,(dp[0][0][0][0]=0)。而根据状态,我们的最终答案就是:(dp[n][0][0][0])。
接下来就是状态转移了:
在我们设计一个(DP)算法的时候,我们需要牢记:(DP)算法的实质其实是一个决策的过程,也就是说,对于每一个状态和阶段,我们如何去根据具体的情况而选择一个当前情况下的最优决策。而由于(DP)的无后效性的影响,这个决策是不会影响到下一个决策的,也就是说,我们每一次进行决策的时候都选择了最优的一种情况,那么最后的答案显然就是最优的。
那么针对于本题,我们的决策应该是什么呢?
就是:放还是不放。即取出来先在手中存着,还是取出来直接放进去。
于是我们就发现,对于第(i)件物品,如果我们存着的话,那么有:
(此处演示的是第(i)件物品为(A)的情况,其他情况同理)
如果装箱的话:
(注意,因为我们每次装箱要装进去所有的同种物品,所以第二维是0)
在我们枚举的时候,还要考虑(j+k+mle 10)。
代码如下:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n;
int dp[110][11][11][11];
char opt[110];
int main()
{
memset(dp,0x3f,sizeof(dp));
scanf("%d",&n);
for(int i=1;i<=n;i++)
cin>>opt[i];
dp[0][0][0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=10;j++)
for(int k=0;k<=10;k++)
for(int m=0;m<=10;m++)
{
if(j+k+m>10)
continue;
if(opt[i]=='A' && j)
dp[i][j][k][m]=dp[i-1][j-1][k][m];
if(opt[i]=='B' && k)
dp[i][j][k][m]=dp[i-1][j][k-1][m];
if(opt[i]=='C' && m)
dp[i][j][k][m]=dp[i-1][j][k][m-1];
dp[i][0][k][m]=min(dp[i][0][k][m],dp[i][j][k][m]+1);
dp[i][j][0][m]=min(dp[i][j][0][m],dp[i][j][k][m]+1);
dp[i][j][k][0]=min(dp[i][j][k][0],dp[i][j][k][m]+1);
}
printf("%d",dp[n][0][0][0]);
return 0;
}