Матрёшка(蓝桥杯)

Матрёшка

参考思路与代码

参考代码
参考思路

问题描述

俄罗斯套娃是一些从外到里大小递减的传统的俄罗斯木头玩偶组成的。当你打开一个俄罗斯套娃时,里面就会露出一个同样的俄罗斯套娃,再打开,就会再露出一个,不断重复。

俄罗斯的俄罗斯套娃博物馆最近收藏了一些外形相似的俄罗斯套娃集,只是里面嵌套的玩偶数量不相等。不幸的是,有一群过分热情的(和明显无人监督的)孩子们拆了他们,并放在一行上。有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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值