做题思路(错解):拿到这道题,在审题时以为队伍中的奶牛一定要分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
#include
#include
#include
#include
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[N][i],d2[N][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;
}