[ SHOI 2001 ] 化工厂装箱员
\(\\\)
\(Description\)
传送带上按顺序传过来\(N\)个物品,一个有\(A,B,C\)三类。
每次装箱员手里只能至多拿十个,然后将手中三类物品中的一类装箱,才能接着拿或接着装箱,求完成整个序列的最少装箱次数。
- \(N\in [1,100]\)
\(\\\)
\(Solution\)
这数据范围不是搜索乱搞
-
\(DP\)。设\(f[s][i][j][k]\)表示,当前已经取走了前\(s\)个,\(A\)类手里有\(i\)个,\(B\)类手里有\(j\)个,\(C\)类手里有\(k\)个,其余取出的全部已经装箱时,最少总装箱次数。有显然边界\(f[0][0][0][0]=1\)。
-
于是暴力枚举状态,复杂度是\(\text O(100\times 10^3)=\text O(10^5)\)的。转移考虑先将手里的东西拿满,再放下其中一类物品。即计算将要达到的位置\(s'\),到\(s'\)时手里三类物品的个数\(x,y,z\),然后就只需要考虑放下哪一类的问题了。
f[sum][0][y][z]=min(f[sum][0][y][z],f[s][i][j][k]+1); f[sum][x][0][z]=min(f[sum][x][0][z],f[s][i][j][k]+1); f[sum][x][y][0]=min(f[sum][x][y][0],f[s][i][j][k]+1);
-
注意到达位置需要考虑序列的边界,答案即为\(f[n][0][0][0]\)。
\(\\\)
\(Code\)
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 110
#define R register
#define gc getchar
#define inf 2000000000
using namespace std;
char c;
int n,ans=inf,a[N],f[N][11][11][11];
int main(){
scanf("%d",&n);
for(R int i=1;i<=n;++i){
c=gc();
while(!isalpha(c)) c=gc();
a[i]=(c=='A'?0:(c=='B'?1:2));
}
for(R int s=0;s<=n;++s)
for(R int i=0;i<=10;++i)
for(R int j=0;j<=10;++j)
for(R int k=0;k<=10;++k) f[s][i][j][k]=inf;
f[0][0][0][0]=0;
for(R int s=0;s<=n;++s)
for(R int i=0;i<=10;++i)
for(R int j=0;j<=10;++j)
for(R int k=0,sum,x,y,z;k<=10;++k)
if(f[s][i][j][k]<inf){
sum=min(n,s+(10-i-j-k)); x=i; y=j; z=k;
for(R int p=s+1;p<=sum;++p) a[p]==0?++x:(a[p]==1?++y:++z);
f[sum][0][y][z]=min(f[sum][0][y][z],f[s][i][j][k]+1);
f[sum][x][0][z]=min(f[sum][x][0][z],f[s][i][j][k]+1);
f[sum][x][y][0]=min(f[sum][x][y][0],f[s][i][j][k]+1);
}
printf("%d\n",f[n][0][0][0]);
return 0;
}