zoukankan      html  css  js  c++  java
  • 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)计算代价我怕是要想一天......
  • 相关阅读:
    学点 C 语言(21): 数据类型 数组与指针
    学点 C 语言(16): 数据类型 关于常量的前缀、后缀
    学点 C 语言(19): 数据类型 数组
    学点 C 语言(15): 数据类型 sizeof(检测类型大小)
    学点 C 语言(18): 数据类型 枚举类型(enum)
    学点 C 语言(20): 数据类型 指针
    重写一个字符串分割函数 回复 "tomzw" 的问题
    学点 C 语言(14): 数据类型 双字节字符类型 wchar_t
    学点 C 语言(22): 数据类型 多维数组与指针
    学点 C 语言(17): 数据类型 因类型引发的问题或错误
  • 原文地址:https://www.cnblogs.com/LSlzf/p/11720964.html
Copyright © 2011-2022 走看看