【牛客网】2017年浙江工业大学大学生程序设计迎新赛热身赛 F题 方块 I【证明题】【想法题】

时间:2021-08-18 10:34:05

【牛客网】2017年浙江工业大学大学生程序设计迎新赛热身赛 F题 方块 I

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 131072K,其他语言262144K
64bit IO Format: %lld
题目描述
有 N 个方块排成一排,每个方块都染有颜色,第 i 个的颜色为 Ci,一共有三种颜色,分别为红,黄,蓝,现在你可以对相邻的颜色不同的方块进行施法,使其变成第三种颜色,比如对相邻的红方块和黄方块进行施法,就会使其合并为蓝方块。施法顺序的不同,可能对最终的结果产生不同的影响,问在最优策略下,最少能剩下多少个方块。

输入描述:
T组数据。
每组数据一行,将方块序列用字符串形式给出,a,b,c表示三种不同颜色的方块。
T <= 10
1 <= N <= 5000
输出描述:
每组数据一个整数,表示答案。


引言:这个题在群里看到有人问,就思考了一下。虽做法简单,证法却不失趣味性,索性久违的写个题解吧~

假设方块段长度 n ,如果只含一种颜色,最少剩余方块数就是 n
如果含多种颜色,可以通过消除一种颜色变成01字符串(ex:0000011110011000)的形式。
我们总可以令01串的的开头是0,并先假设01串段数至少为3,段数为2特殊处理。
主方法:取从左往右第一个1往左一直合并,根据0的个数会变成1或者2(被消除的第三种颜色)。接下来分两种情况:
1. 如果变成1,就相当于最开始的0段被消除,1段增加了一个,成了递归形式,把01串01翻转就可以了。
2. 如果变成2,则串形式变成”21111000……”,接下来用2向右合并,合并掉右边的1段后,01串又有两种分歧:
2.1 “2000……”,此时只要01翻转就是情况2的递归形式。
2.2 “0000……”,此时串为原始形态的递归形式。

当递归到最后除开头可能有的2以外,01串仅剩两段时(ex:00001111),需要特别处理。根据开头有无2、0个数奇偶,分四种情况。
开头无2,0个数为奇(ex:000001111):采用主方法,用右边第一个1向左合并,串变为2111形式,再用2向右合并,最后一定剩一个,至于是0还是2,看1的个数吧。
开头无2,0个数为偶(ex:00001111):直接合并掉最左边的所有0,会变成1111,肯定不能这么做。解决方法是先隔离最左边的0,当做情况【开头无2,0个数为奇】处理,然后再尝试用处理后结果尝试和0合并。如果1个数为奇则最后可以合并为1,否则剩两个0。
开头有2,0个数为奇(ex:20001111):向右合并到剩一个0,变成”201111”,接下来用0向右合并,最后尝试和开头的2合并。1个数为奇则剩两个2,1个数为偶剩一个1。
开头有2,0个数为偶(ex:200001111):不多说,2一直向右合并即可。1个数为偶剩一个2,1个数为奇剩一个0。

得证:非纯色方块段,最后必可合并成2个或者1个方块。

注意,接下来才是本题的解法!

根据上面的证明可知非纯色方块段最后一定可以剩2个或1个方块,那2个能否合并成一个?

引入一个概念:三种颜色分别用0,1,2表示。题意所示合并方式即01合并等于2,02合并等于1,12合并等于0。该合并方式可以用公式表示:
0^1^3=2
0^2^3=1
1^2^3=0
可以很容易找到规律,两颜色合并变成第三种颜色,即代表的数字 xor xor 上3后的答案!

该规律有什么用?

只要出现合并就会异或上一个3,一个长度为 n 的方块段如果合并成1个方块,最后剩余方块的颜色就是 n 个颜色代表的数字(0,1,2) xor 后再 xor n -1个3的值所代表的颜色。

由于一开始我们证明了非纯色方块段必能合并成剩1或2个方块。那最后2个方块能否合并成1个方块,只要看n个数 xor 后再 xor n -1个3的值是否不为3了(因为 x ^ x ^3=3),如果不为3说明 n 个方块最后能合并成一个,且最后的颜色也可以确定。否则就是两个方块。

最终解法异常简单:三种颜色分别用0,1,2表示,然后先判断 n 个数是否相同。是的话就输出 n ;不是就全部 xor 起来再 xor n -1个3,如果等于3输出2否则输出1。

PS:如果有更加简单巧妙的证明,可以留言~

my code:

#include <bits/stdc++.h>
using namespace std ;

const int MAXN = 5005 ;

char s[MAXN] ;

void solve () {
int n = strlen ( s ) ;
char c = s[0] ;
int ans = n % 2 ? 0 : 3 , cnt = 0 ;
for ( int i = 0 ; i < n ; ++ i ) {
ans ^= s[i] - 'a' ;
if ( s[i] == c ) ++ cnt ;
}
if ( cnt == n ) printf ( "%d\n" , n ) ;
else printf ( "%d\n" , ans == 3 ? 2 : 1 ) ;
}

int main () {
while ( ~scanf ( "%s" , s ) ) solve () ;
}