问题描述

家庭菜园专家 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)计算代价我怕是要想一天......

JOI2019 有趣的家庭菜园3的更多相关文章

  1. 【BZOJ4240】有趣的家庭菜园 树状数组+贪心

    [BZOJ4240]有趣的家庭菜园 Description 对家庭菜园有兴趣的JOI君每年在自家的田地中种植一种叫做IOI草的植物.JOI君的田地沿东西方向被划分为N个区域,由西到东标号为1~N.IO ...

  2. bzoj4240: 有趣的家庭菜园(树状数组+贪心思想)

    4240: 有趣的家庭菜园 题目:传送门 题解: 好题!%%% 一开始不知道在想什么鬼,感觉满足二分性?感觉可以维护一个先单调增再单调减的序列? 然后开始一顿瞎搞...一WA 看一波路牌...树状数组 ...

  3. [bzoj4240]有趣的家庭菜园_树状数组

    有趣的家庭菜园 题目链接:https://lydsy.com/JudgeOnline/problem.php?id=4240 数据范围:略. 题解: 第一步比较简单,只需要排序之后,每个数不是在左边就 ...

  4. bzoj4240有趣的家庭菜园(贪心+逆序对)

    对家庭菜园有兴趣的JOI君每年在自家的田地中种植一种叫做IOI草的植物.JOI君的田地沿东西方向被划分为N个区域,由西到东标号为1~N.IOI草一共有N株,每个区域种植着一株.在第i个区域种植的IOI ...

  5. 【bzoj4240】有趣的家庭菜园 贪心+树状数组

    题目描述 对家庭菜园有兴趣的JOI君每年在自家的田地中种植一种叫做IOI草的植物.JOI君的田地沿东西方向被划分为N个区域,由西到东标号为1~N.IOI草一共有N株,每个区域种植着一株.在第i个区域种 ...

  6. [bzoj4240] 有趣的家庭菜园

    还是膜网上题解QAQ 从低到高考虑,这样就不会影响后挪的草了. 每次把草贪心地挪到代价较小的一边.位置为i的草,花费为min( 1..i-1中更高的草的数目,i+1..n中更高的草的数目 ) 因为更小 ...

  7. 【bzoj4240】 有趣的家庭菜园 树状数组

    这一题最终要构造的序列显然是一个单峰序列 首先有一个结论:一个序列通过交换相邻的元素,进行排序,最少的交换次数为该序列的逆序对个数 (该结论很久之前打表意外发现的,没想到用上了.....) 考虑如何构 ...

  8. [BZOJ4240]有趣的家庭菜园(贪心+树状数组)

    最后数列一定是单峰的,问题就是最小化最后的位置序列的逆序对数. 从大到小加数,每次贪心看放左边和右边哪个产生的逆序对数更少,树状数组即可. 由于大数放哪对小数不产生影响,所以正确性显然. 注意相同数之 ...

  9. BZOJ4240 有趣的家庭菜园(贪心+树状数组)

    显然相当于使序列变成单峰.给原序列每个数按位置标号,则要求重排后的序列原标号的逆序对数最少.考虑将数从大到小放进新序列,那么贪心的考虑放在左边还是右边即可,因为更小的数一定会在其两侧,与它自身放在哪无 ...

随机推荐

  1. java数字加密算法

    数字加密在项目中时常会遇到,如手机号,身份证号信息等,下面小白将自己手写的数字加密算法分享给大家,可在项目中直接运用.加密规则,入参时传递一个字段时间戳 time:* 1.以字母代替数字,0-9分别为 ...

  2. 阶段3 1.Mybatis_09.Mybatis的多表操作_5 完成user的一对多查询操作

    定义List<Account> accounts,生成getter和setter 复制AccountTest类改名UserTest类 修改测试类 还没封装所以Account的list都是n ...

  3. DedeCMS调取其他织梦CMS站点数据库数据方法

    第1步:打开网站include\taglib文件夹中找到sql.lib.php文件,并直接复制一些此文件出来,并把复制出来的这个文件重命名为mysql.lib.php.注:mysql.lib.php, ...

  4. shoi 魔法树

    Harry Potter新学了一种魔法:可以改变树上的果子个数.满心欢喜的他找到了一个巨大的果树,来试验他的新法术.这棵果树共有N个节点,其中节点0是根节点,每个节点u的父亲记为fa[u],保证有fa ...

  5. 多线程17-Async Programming Model

        );             ThreadId = Thread.CurrentThread.ManagedThreadId;             ;             RunOnT ...

  6. 【转】mysqldump原理探究

    作者:胡儿胡儿 来源:CSDN 原文:https://blog.csdn.net/cug_jiang126com/article/details/49824471 —————————————————— ...

  7. 使用eclipse创建mavenWeb项目,中途遇到的问题及解决方案!

    创建MavenWeb项目的步骤,如下: 1).new--->Maven --->maven project,如图: 之后, next,最后finish,项目创建完成,项目的目录如下: 将w ...

  8. HTTP/2 最新漏洞,直指 Kubernetes!

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 在这个数据.应用横行的时代,漏洞的出现早已屡见不鲜.在尚未造成大面积危害之前,我们该如何做好防御措施?或许从过往经常发生漏洞 ...

  9. HNUSTOJ-1695 跳格子(略感头疼)

    1695: 跳格子 时间限制: 1 Sec  内存限制: 128 MB提交: 230  解决: 57[提交][状态][讨论版] 题目描述 逸夫楼的大厅的地面有10行10列的石砖,我们用坐标(x,y)来 ...

  10. Node.js+koa2

    const Koa = require('koa') const app = new Koa() const bodyParser = require('koa-bodyparser') app.us ...