Матрёшка
参考思路与代码
问题描述
俄罗斯套娃是一些从外到里大小递减的传统的俄罗斯木头玩偶组成的。当你打开一个俄罗斯套娃时,里面就会露出一个同样的俄罗斯套娃,再打开,就会再露出一个,不断重复。
俄罗斯的俄罗斯套娃博物馆最近收藏了一些外形相似的俄罗斯套娃集,只是里面嵌套的玩偶数量不相等。不幸的是,有一群过分热情的(和明显无人监督的)孩子们拆了他们,并放在一行上。有n个玩偶在一上,每个都有一个整数的大小,你需要重新组装套娃集,你既不知道套娃集的数量,也不知道某个套娃集内玩偶的数量,你只知道一个完好的套娃集内的玩偶大小是从1到某个数字m
在组装套娃集时,你必须遵守下列规则:
1.你只能将一个玩偶或者套娃集放入一个更大的玩偶中
2.你只能把相邻两个俄罗斯套娃组合在一起
3.已经被合并的玩偶是不能再重新拆出来的。
你的时间很宝贵,你只想尽快的组装好。唯一需要耗时的部分为打开一个玩偶并马上关上它。所以你要尽可能少的做这种操作。比如说:合并[1,2,6]与[4],你需要将大小为4和6的两个玩偶拆开。合并[1,2,5]与[3,4]代价为3。
求将n个玩偶重新拼成一些完好的俄罗斯套娃的最小代价。
对代码的理解与阅读
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=510,inf=0x3f3f3f3f;
int dp[maxn][maxn],f[maxn],sum[maxn][maxn],ms[maxn][maxn],n,m,A[maxn];//dp[i][j]表示第 i到第j个娃套起来需要的最小次数,f[i]表示前i个套一起的最小次数,sum[i][j]表示前i个中小于等于j的有几个,ms[i][j]表示 i到j中最小的娃是多大
int calc(int a,int b,int c,int d)
{
int m1=ms[a][b],m2=ms[c][d];
int ans=inf;
ans=(b-a+1)-(sum[b][m2]-sum[a-1][m2])+(d-c+1);//集合a--b中大于min(c--d)的全打开,c--d全打开耗费的次数
ans=min(ans,(d-c+1)-(sum[d][m1]-sum[c-1][m1])+(b-a+1));//选择 集合c--d中大于min(a--b)的全打开,a--b全打开耗费的次数 与上一行打开方式中次数小的
return ans;
}
bool B[maxn];
bool mex(int a,int b) //判断区间内是否有相同大小的套娃出现,若有则返回0,该区间不成立
{
memset(B,0,sizeof(B));
for(int i=a;i<=b;i++)
if(B[A[i]])
return 0;
else
B[A[i]]++;
bool ok=0;
if(!B[1])
return 0;
for(int i=1;i<=m;i++)
{
if(ok&&B[i])
return 0;
if(B[i-1]&&!B[i])
ok=1;
}
return 1;
}
int main()
{
/*printf("%d\n",inf);*/
int k,l;
scanf("%d",&n);
m=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&A[i]);
m=max(m,A[i]+1);
}
// printf("%d,m\n",m);
for(int i=1;i<=n;i++) //录入每个娃的大小后得到最大值,从i到n个娃,前i个娃中小于a[i]的娃的个数放入sum[i][a[i]]中,初始化sum
{
for(int j=1;j<=A[i];j++)
sum[i][j]=sum[i-1][j];
/* for(k=0;k<=n;k++)
{
for(l=0;l<=n;l++)
printf("%d ",sum[k][l]);
printf("\n");
}
printf("\n\n");*/
for(int j=A[i];j<m;j++)
sum[i][j]=sum[i-1][j]+1;
/* for(k=0;k<=n;k++)
{
for(l=0;l<=n;l++)
printf("%d ",sum[k][l]);
printf("\n");
}
printf("\n\n");
*/
ms[i][i]=A[i];
for(int j=i+1;j<=n;j++) //录入第i--j中最小的娃是多大号
ms[i][j]=min(ms[i][j-1],A[j]);
/* printf("ms\n\n");
for(k=0;k<=n;k++)
{
for(l=0;l<=n;l++)
printf("%d ",ms[k][l]);
printf("\n");
}
printf("\n\n");
*/
}
for(int p=1;p<=n-1;p++) //p为i、j的间隔个数,最大是i==1,j==7 p==6,间隔为1--6时,i--j所有的情况枚举
{
for(int i=1;i<=n-p;i++)//将第i到j个娃套起来需要的最小次数(i==1j==2,i==2 j==3,i==6 j==7;i==1 j==3... i==5 j==6;...i==1 j==7;
{
int j=i+p;
dp[i][j]=inf;
for(int k=i;k<j;k++)//分为第i--k个,k+1--j个
if(dp[i][k]<inf&&dp[k+1][j]<inf)//确定i--j可以从k分开
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+calc(i,k,k+1,j));
}
/*
printf("dp\n\n");
for(k=0;k<=n;k++)
{
for(l=0;l<=n;l++)
printf("%d ",dp[k][l]);
printf("\n");
}
printf("\n\n");
*/
}
/* printf("dp\n\n");
for(k=0;k<=n;k++)
{
for(l=0;l<=n;l++)
printf("%d ",dp[k][l]);
printf("\n");
}
printf("\n\n");
*/
memset(f,0x3f,sizeof(f));
f[0]=0;//初始化!!!
for(int i=1;i<=n;i++) //前i个套一起的最小打开次数
{
for(int j=0;j<i;j++)
if(f[j]<inf-100)
{
if(mex(j+1,i))
{
f[i]=min(f[i],f[j]+dp[j+1][i]);//第0-i套在一起需要的打开次数,与0-j个套一起加上j+1到i个套一起的打开次数比较
}
}
}
/* printf("f\n\n");
for(l=0;l<=n;l++)
printf("%d ",f[l]);
printf("\n");
printf("\n\n");
*/
if(f[n]>inf-100)
printf("Impossible");
else printf("%d",f[n]);
return 0;
}
c语言代码复现
#include<stdio.h>
#define inf 0x3f3f3f3f
int a[510],f[510],boolean[510]={0},dp[510][510],ms[510][510],sum[510][510]={0};
int m=0;
int max(int x,int y)
{
if(x>=y)
return x;
else
return y;
}
int min(int x,int y)
{
if(x>=y)
return y;
else
return x;
}
int calc(int a,int b,int c,int d)
{
int m1=ms[a][b],m2=ms[c][d];
int ans=inf;
ans=(b-a+1)-(sum[b][m2]-sum[a-1][m2])+(d-c+1);
ans=min(ans,(d-c+1)-(sum[d][m1]-sum[c-1][m1])+(b-a+1));
return ans;
}
int mex(int x,int y)
{
memset(boolean,0,sizeof(boolean));
int i=0,ai=0,ok=0;
for(i=x;i<=y;i++)
{
ai=a[i];
if(boolean[ai])
return 0;
else
boolean[ai]++;
}
if(!boolean[1])
return 0;
for(i=1;i<=m;i++)
{
if(ok&&boolean[i])
return 0;
if(boolean[i-1]&&!boolean[i])
ok=1;
}
return 1;
}
int main()
{
int i=0,j=0,n=0,p=0,k=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
m=max(m,a[i]+1);
}
for(i=1;i<=n;i++)
{
for(j=1;j<=a[i];j++)
sum[i][j]=sum[i-1][j];
for(j=a[i];j<m;j++)
sum[i][j]=sum[i-1][j]+1;
ms[i][i]=a[i];
for(j=i+1;j<=n;j++)
{
ms[i][j]=min(ms[i][j-1],a[j]);
}
}
for(p=1;p<=n-1;p++)
{
for(i=1;i<=n-p;i++)
{
j=i+p;
dp[i][j]=inf;
for(k=i;k<j;k++)
if(dp[i][k]<inf&&dp[k+1][j]<inf)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+calc(i,k,k+1,j));
}
}
for(i=0;i<510;i++)
{
f[i]=inf;
}
f[0]=0;
for(i=1;i<=n;i++)
{
for(j=0;j<i;j++)
if(f[j]<inf)
{
if(mex(j+1,i))
f[i]=min(f[i],f[j]+dp[j+1][i]);
}
}
if(f[n]==inf)
printf("Impossible");
else
printf("%d",f[n]);
return 0;
}