• JOI2019 有趣的家庭菜园3


    问题描述

    家庭菜园专家 JOI 先生在他的家庭菜园中种植了一种叫 Joy 草的植物。在他的菜园里,有 N 个花盆自东向西摆放,编号分别为 (1, ldots, N)。每个花盆中有一株 Joy 草。

    春天到了,JOI 先生注意到 Joy 草如他期望地长出了各种颜色的叶子,但他也发现 Joy 草的生长速度没有他期望的那么快。他查阅了书籍,找到了草的以下特点:

    • Joy 草有三种品种,分别会长出红色、绿色和黄色的叶子。

    • 如果两株同一颜色的 Joy 草紧密相邻,它们的生长速度就会减慢。

    因此,JOI 先生决定重新摆放花盆,使得没有两株相邻的 Joy 草颜色相同。

    花盆非常沉重,因此 JOI 先生每次只能交换相邻的两个花盆。形式化的说,JOI 先生每次操作可以选择一个$ i (1 le i < N)$,然后交换花盆 i 和花盆 i+1。

    请编写一个程序,计算最少的交换次数。

    解析

    考虑将当前在哪个位置作为阶段,那么我们需要知道前一个阶段的状态。显然,我们需要知道交换后1到i-1每种颜色的草的数量,转移时也需要知道第i-1为放的是什么草。设(f[i][j][k][0/1/2])表示当前完成前i个花盆,交换后有j个红花,k个黄花,i-j-k个绿花,第i为放第0/1/2种花时的最小交换次数。不妨设将第x朵颜色为op的花移动到y位置的交换次数为(cost(op,x,y)),那么,我们有如下状态转移方程:

    [f[i][j][k][0]=min(f[i-1][j][k][1]+cost(0,i-j-k+1,i),f[i-1][j][k][2]+cost(0,i-j-k+1,i)) ]

    其他的同理。现在讨论如何(O(1))(cost)值。记第x朵颜色为op的花在原序列中出现的位置为(pos[op][x]),那么将这朵花移动到i位置需要(pos[op][x]-i)步。但是,有可能在之前移动了其他颜色的花到区间([1,i])中,所以移动的长度也要大于之前的值。具体见代码。

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define N 402
    using namespace std;
    const int inf=1<<30;
    int n,i,j,k,x,f[2][N][N][3],pos[3][N],sum[3][N],a[N],cnt[3];
    char c[N];
    int cal(int op,int g,int r,int y)
    {
    	int m=g+r+y+1;
    	if(g>cnt[0]||r>cnt[1]||y>cnt[2]) return inf;
    	if(op==0){
    		if(g+1>cnt[0]) return inf;
    		int p=pos[0][g+1];
    		return p-m+max(0,r-sum[1][p])+max(0,y-sum[2][p]);
            //sum[op][p]表示在[1,p]中有多少颜色为op的花。
            //r-sum[1][p]即在此之前移动了多少红花到[1,i]中来,那么绿花移动的长度也要加上r-sum[1][p]。
            //当然,如果没有多移动,就不需要加了。
            //y-sum[2][p]也是同样的道理。
    	}
    	else if(op==1){
    		if(r+1>cnt[1]) return inf;
    		int p=pos[1][r+1];
    		return p-m+max(0,g-sum[0][p])+max(0,y-sum[2][p]);
    	}
    	else{
    		if(y+1>cnt[2]) return inf;
    		int p=pos[2][y+1];
    		return p-m+max(0,g-sum[0][p])+max(0,r-sum[1][p]);
    	}
    }
    int main()
    {
    	cin>>n>>c;
    	for(i=1;i<=n;i++){
    		if(c[i-1]=='R') a[i]=1;
    		else if(c[i-1]=='Y') a[i]=2;
    	}
    	for(i=1;i<=n;i++) pos[a[i]][++cnt[a[i]]]=i;
    	for(i=1;i<=n;i++){
    		for(j=0;j<3;j++) sum[j][i]=sum[j][i-1];
    		sum[a[i]][i]++;
    	}
    	memset(f,0x3f,sizeof(f));
    	f[0][0][0][0]=pos[0][1]-1;
    	f[0][1][0][1]=pos[1][1]-1;
    	f[0][0][1][2]=pos[2][1]-1;
    	for(i=2;i<=n;i++){
    		x^=1;
    		memset(f[x],0x3f,sizeof(f[x]));
    		for(j=0;j<=(i+1)/2;j++){
    			for(k=0;k<=i-j&&k<=(i+1)/2;k++){
    				if(i-j-k){
    					f[x][j][k][0]=min(f[x][j][k][0],f[x^1][j][k][1]+cal(0,i-j-k-1,j,k));
    					f[x][j][k][0]=min(f[x][j][k][0],f[x^1][j][k][2]+cal(0,i-j-k-1,j,k));
    				}
    				if(j){
    					f[x][j][k][1]=min(f[x][j][k][1],f[x^1][j-1][k][0]+cal(1,i-j-k,j-1,k));
    					f[x][j][k][1]=min(f[x][j][k][1],f[x^1][j-1][k][2]+cal(1,i-j-k,j-1,k));
    				}
    				if(k){
    					f[x][j][k][2]=min(f[x][j][k][2],f[x^1][j][k-1][0]+cal(2,i-j-k,j,k-1));
    					f[x][j][k][2]=min(f[x][j][k][2],f[x^1][j][k-1][1]+cal(2,i-j-k,j,k-1));
    				}
    			}
    		}
    	}
    	int ans=inf;
    	for(i=0;i<=n;i++){
    		for(j=0;j<=n;j++){
    			for(k=0;k<3;k++) ans=min(ans,f[x][i][j][k]);
    		}
    	}
    	if(ans>=1000000000) cout<<"-1"<<endl;
    	else if(ans<0) cout<<"0"<<endl;
    	else cout<<ans<<endl;
    	return 0;
    }
    

    反思

    • 打暴搜之前要特判是否在开始就是合法的情况,这样就可以输出0了。
    • 其实如果明确了阶段的定义,并想到如何表示一个阶段,状态转移应该也不难想到了。不过O(1)计算代价我怕是要想一天......
  • 相关阅读:
    什么样的代码称得上是好代码?
    九年程序人生 总结分享
    Docker入门 第一课 --.Net Core 使用Docker全程记录
    阿里云 Windows Server 2012 r2 部署asp.net mvc网站 平坑之旅
    Visual studio 2015 Community 安装过程中遇到问题的终极解决
    Activiti6.0 spring5 工作流引擎 java SSM流程审批 项目框架
    java 进销存 库存管理 销售报表 商户管理 springmvc SSM crm 项目
    Leetcode名企之路
    24. 两两交换链表中的节点
    21. 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/LSlzf/p/11720964.html
Copyright © 2020-2023  润新知