您的位置:首页 > Web前端

【RQNOJ 285】USACO2008 Feb 麻烦的聚餐(重庆一中高2018级信息学竞赛测验9) 解题报告

2016-09-10 17:03 399 查看
【问题描述】

  为了避免餐厅过分拥挤,FJ要求奶牛们分3批就餐。每天晚饭前,奶牛们都会在餐厅前排队入内,按FJ的设想,所有第3批就餐的奶牛排在队尾,队伍的前端由设定为第1批就餐的奶牛占据,中间的位置就归第2批就餐的奶牛了。由于奶牛们不理解FJ的安排,晚饭前的排队成了一个大麻烦。

  第i头奶牛有一张标明她用餐批次 D_i 的卡片。虽然所有N头奶牛排成了很整齐的队伍,但谁都看得出来,卡片上的号码是完全杂乱无章的。

  在若干次混乱的重新排队后,FJ找到了一种简单些的方法:奶牛们不动,他沿着队伍从头到尾走一遍,把那些他认为排错队的奶牛卡片上的编号改掉,最终得到一个他想要的每个组中的奶牛都站在一起的队列,例如111222333或者333222111。哦,你也发现了,FJ不反对一条前后颠倒的队列,那样他可以让所有奶牛向后转,然后按正常顺序进入餐厅。

  你也晓得,FJ是个很懒的人。他想知道,如果他想达到目的,那么他最少得改多少头奶牛卡片上的编号。所有奶牛在FJ改卡片编号的时候,都不会挪位置。

【输入格式】

  第1行: 1个整数:N

  第2..N+1行: 第i+1行是1个整数,为第i头奶牛的用餐批次D_i

【输出格式】

  第1行: 输出1个整数,为FJ最少要改几头奶牛卡片上的编号,才能让编号变成他设想中的样子

【输入样例】

5

1

3

2

1

1

【输出样例】

1

【数据范围】

  1<=D_i<= 3

  1<=N<=30,000

【来源】

  队列中共有5头奶牛,第1头以及最后2头奶牛被设定为第一批用餐,第2头奶牛的预设是第三批用餐,第3头则为第二批用餐。如果FJ想把当前队列改成一个不下降序列,他至少要改2头奶牛的编号,一种可行的方案是:把队伍中2头编号不是1的奶牛的编号都改成1。不过,如果FJ选择把第1头奶牛的编号改成3,就能把奶牛们的队伍改造成一个合法的不上升序列了。

做题思路(错解):拿到这道题,在审题时以为队伍中的奶牛一定要分3批,于是推出了不完整的状态转移方程和边界,最后只得了40分。

解题思路(正解):根据题意,思路应该明确,主要算法就是动态规划,但为了方便分析状态转移方程,将之前顺序的队列与前后颠倒的队列分开考虑。

设状态函数f(i,j)表示前i头奶牛中,第i头奶牛卡片编号为j时的最少改动次数,接下来分析状态转移方程,第i头奶牛的编号可以为1、2、3,如果第i头奶牛原来的编号与现在的编号不符,则改动次数就要加1,当第i头奶牛的编号为1时,它前面的奶牛编号只能为1;当第i头奶牛的编号为2时,它前面的奶牛编号可以为1或者2;当第i头奶牛的编号为3时,因为编号为2的部分可以空缺,所以它前面的奶牛编号可以为1或者2或者3。所以,状态转移方程为if(a[i]!=j) f(i,3)=min{f(i-1,1),f(i-1,2),f(i-1,3)}+1
f(i,2)=min{f(i-1,1),f(i-1,2)}+1 f(i,1)=f(i-1,1)+1,if(a[i]==j) f(i,3)=min{f(i-1,1),f(i-1,2),f(i-1,3)} f(i,2)=min{f(i-1,1),f(i-1,2)} f(i,1)=f(i-1,1)。因为队伍中的奶牛不一定要分3批,所以边界条件为if(a[1]!=1) f(1,1)=1 else f(1,1)=0 ; if(a[1]!=2) f(1,2)=1 else f(1,2)=0
; if(a[1]!=3) f(1,3)=1 else f(1,3)=0。答案即在f(N,1),f(N,2),f(N,3)中取最小值。该算法的时间复杂度为O(3*n)。

因为队列可以前后颠倒,所以想要计算前后颠倒的序列满足条件时的最少改动次数,只需将输入的数据反向存储即可,动态规划与顺序的队列相同。

值得一提的是,该题的动态规划还有另外的做法,见麻烦的聚餐 解法2 from cqyz_yoyoku

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=30005;
int N;
int a[maxn],b[maxn];
/*
f(i,j)表示前i头奶牛中,第i头奶牛卡片编号为j时的最少改动次数
if(a[i]!=j)  f(i,3)=min{f(i-1,1),f(i-1,2),f(i-1,3)}+1
f(i,2)=min{f(i-1,1),f(i-1,2)}+1
f(i,1)=f(i-1,1)+1
if(a[i]==j)  f(i,3)=min{f(i-1,1),f(i-1,2),f(i-1,3)}
f(i,2)=min{f(i-1,1),f(i-1,2)}
f(i,1)=f(i-1,1)
边界:if(a[1]!=1)  f(1,1)=1
else  f(1,1)=0
if(a[1]!=2)  f(1,2)=1
else  f(1,2)=0
if(a[1]!=3)  f(1,3)=1
else  f(1,3)=0
*/
int d1[maxn][5],d2[maxn][5];
void solve()  //动态规划
{
memset(d1,100,sizeof(d1));
memset(d2,100,sizeof(d2));
if(a[1]!=1)  d1[1][1]=1;
else  d1[1][1]=0;
if(a[1]!=2)  d1[1][2]=1;
else  d1[1][2]=0;
if(a[1]!=3)  d1[1][3]=1;
else  d1[1][3]=0;
if(b[1]!=1)  d2[1][1]=1;
else  d2[1][1]=0;
if(b[1]!=2)  d2[1][2]=1;
else  d2[1][2]=0;
if(b[1]!=3)  d2[1][3]=1;
else  d2[1][3]=0;
for(int i=2;i<=N;i++)
{
for(int j=1;j<=3;j++)
{
if(a[i]!=j)
{
if(j==1)  d1[i][1]=d1[i-1][1]+1;
if(j==2)  d1[i][2]=min(d1[i-1][1],d1[i-1][2])+1;
if(j==3)  d1[i][3]=min(d1[i-1][1],min(d1[i-1][2],d1[i-1][3]))+1;
}
else
{
if(j==1)  d1[i][1]=d1[i-1][1];
if(j==2)  d1[i][2]=min(d1[i-1][1],d1[i-1][2]);
if(j==3)  d1[i][3]=min(d1[i-1][1],min(d1[i-1][2],d1[i-1][3]));
}
}
}
for(int i=2;i<=N;i++)
{
for(int j=1;j<=3;j++)
{
if(b[i]!=j)
{
if(j==1)  d2[i][1]=d2[i-1][1]+1;
if(j==2)  d2[i][2]=min(d2[i-1][1],d2[i-1][2])+1;
if(j==3)  d2[i][3]=min(d2[i-1][1],min(d2[i-1][2],d2[i-1][3]))+1;
}
else
{
if(j==1)  d2[i][1]=d2[i-1][1];
if(j==2)  d2[i][2]=min(d2[i-1][1],d2[i-1][2]);
if(j==3)  d2[i][3]=min(d2[i-1][1],min(d2[i-1][2],d2[i-1][3]));
}
}
}
int ans=1000000010;
for(int i=1;i<=3;i++)
ans=min(ans,min(d1
[i],d2
[i]));
printf("%d\n",ans);
}
int main()
{
freopen("dinner.in","r",stdin);
//freopen("dinner.out","w",stdout);
scanf("%d",&N);
for(int i=1;i<=N;i++)
{
scanf("%d",&a[i]);
b[N-i+1]=a[i];  //反向存储输入数据,即将原队列前后颠倒
}
solve();
return 0;
}


考后反思:还是要注意审题,一定要仔细理解题目的意思,以防因对题目的曲解而造成的失误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐