传送门:http://lx.lanqiao.cn/problem.page?gpid=T507
问题描述
盘子装运公司是一家网络零售商,顾名思义,是一家只销售盘子的公司。该公司销售的盘子由不计其数的生产厂商提供,品种是全宇宙最多的,为此公司的员工倍感自豪。
在最近的一次成本分析中,公司员工发现,他们花费了大量金钱在盘子的装箱环节。一部分原因是盘子在被运输工具运走前,需要被堆成一堆。很显然,这个阶段较预期浪费了大量的时间。或许你可以为公司提供帮助。
一次装运的盘子由若干厂商生产的盘子组合而成。各家厂商将其生产的盘子从小到大堆成一堆(小盘在上,大盘在下),再运送到公司。我们称上述按照顺序排列的盘堆顺序合理。为了方便装运,你必须将来自各个厂商的盘子重新组合成顺序合理的一堆。将来自各厂商的盘堆组合成一个盘堆时,可以进行如下两种操作:
拆分(Split):可以将一个盘堆堆顶任意数目的盘子抬起,并放置在原堆的一侧,使堆一分为二。
合并(Join):可以将一个盘堆放置在另一堆的堆顶,前提是在上方的堆最底层的盘子尺寸不大于在下方的堆最顶层的盘子尺寸。
注意不能将一个盘堆堆顶上的若干盘子直接移动到另一个盘堆的堆顶,必须先拆分,后合并。给定盘堆若干,你需要求出将这些盘堆通过两种操作合并成一堆的最少次数。下面的图是对输入输出样例的解释。
输入格式
本题有多组输入数据。每组输入的第一行只有一个整数n,表示盘堆的数目,接下来的n行是对每个盘堆的描述,第i+1行的第一个整数hi为第i个盘堆的盘子数,之后有hi个正整数,为盘堆从上至下各个盘子的直径(直径为不超过10000的正整数),这些整数保证按照从小到大的顺序排列。
输出格式
对于每组数据,输出数据编号以及将n个盘堆按照规则合并为一个盘堆的最少操作次数。
数据范围
n<=50 h≤50 数据组数<=400
本题出自蓝桥杯训练题库
还不错的一道题,但是被垃圾蓝桥OJ毁了(C++98,以及奇慢无比的评测效率)
想象堆放过程,如果盘堆我们最后要分成m个新堆再进行合并,那么答案就是
2
∗
m
−
n
−
1
2*m-n-1
2∗m−n−1。心中思考一下我们一般人是怎么分m堆的,倘若每个盘子的大小互不相同,然后我们不断从这n个盘子队列中取出最小的队头,如果连续不断地在某个队列里取多次,那么这几次取出的盘子我们把它们作为一堆一起取出,这样是新的堆数最少,就是最优的。
现在需要考虑存在盘子大小相同的情况,也就是如果多个队头都相同,取哪一个呢?没有固定答案,需要动态规划。
我们记录一下每一种大小的盘子出现在了那些堆中。用
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示枚举到大小为i,枚举的最后一个盘子来自编号为j的堆。如果我们希望取出顺序能使得堆数最优,则在取出序列中,同一个大小的盘子们属于的堆相同则必然相邻。
情况A:第i步只有来自一个堆的盘子
情况B: 除了A,第i步只有一种盘子第i步的首个盘子和上一步的最后一个盘子属于不同堆
情况C: 除了A,第i步只有一种盘子第i步的首个盘子和上一步的最后一个盘子属于相同堆
很容易列出一个规模
h
2
h^2
h2的转移方程。但最后两个点还是需要继续优化,可以预处理,然后变成规模为
h
h
h,这样效率就是
T
∗
10000
∗
h
T*10000*h
T∗10000∗h
然而还没完,一开始我用的是vector作记录盘子出现在了那些堆,二分查找来找某个堆是否存在……结果太慢了,只能改用布尔数组。而且因为实际的盘的大小最多只有2500种,还需要离散化一下(天哪,10000和2500的常数OJ都卡掉了我也是醉了),于是改来改去成了下面这坨代码了QwQ。
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
bool V[2505][55]; //记录一下每一种大小的盘子出现在了那些堆中, c记录个数
int n,dp[2505][55],kase,c[2505],t1[55],t2[55],inf=1E9,sz;
int a[55][55],p[55],L[10005];
int main()
{
while(scanf("%d",&n)==1)
{
++kase;
for(int i=1;i<=sz;i++)
memset(V[i],0,sizeof(V[i])),c[i]=0;
sz=0;
memset(L,0,sizeof(L));
memset(p,0,sizeof(p));
for(int i=1;i<=n;i++)
{
scanf("%d",&p[i]);
for(int j=1;j<=p[i];j++)
scanf("%d",&a[i][j]),L[a[i][j]]=1;
}
for(int i=1;i<=10000;i++)
if(L[i])
L[i]=++sz; //这里搞了离散化
for(int i=1;i<=n;i++)
for(int j=1;j<=p[i];j++)
V[L[a[i][j]]][i]=true;
for(int i=1;i<=sz;i++)
for(int j=1;j<=50;j++)
c[i]+=V[i][j];
for(int i=1;i<=50;i++)
dp[0][i]=0;
for(int i=1;i<=sz;i++)
{
int tmp=*min_element(dp[i-1]+1,dp[i-1]+51)+c[i];
for(int j=1;j<=50;j++)
dp[i][j]=V[i][j]?tmp:inf; //情况B
for(int j=0;j<=51;j++)
t1[j]=t2[j]=inf;
if(c[i]==1)
for(int j=1;j<=50;j++)
if(V[i][j])
{
dp[i][j]=i>1?min(dp[i][j],dp[i-1][j]):1; //情况A
break;
}
for(int j=1;j<=50;j++)
t1[j]=min(t1[j-1],V[i-1][j]&&V[i][j]?dp[i-1][j]+c[i]-1:inf);
for(int j=50;j>0;j--)
t2[j]=min(t2[j+1],V[i-1][j]&&V[i][j]?dp[i-1][j]+c[i]-1:inf);
for(int k=1;k<=50;k++)
if(V[i][k])
dp[i][k]=min(dp[i][k],min(t1[k-1],t2[k+1])); //情况C
}
int ans=1E9;
for(int i=1;i<=50;i++)
ans=min(ans,dp[sz][i]);
printf("Case %d: %d\n",kase,2*ans-n-1);
}
return 0;
}