题解:小蒟蒻的子序列

题目描述

小蒟蒻最近对字符串的子序列着了迷。一个字符串 s 被称作另一个字符串 S 的子序列,说明从序列 S 去除某些元素但不破坏余下元素的相对位置(在前或在后)可得到序列 s 。

小蒟蒻想到了如下的问题:给出一个由’a’, ’b’, ’c’ 组成的长度为 n 的字符串。

定义一个子序列 T 的价值如下:若子序列 T存在循环节,则其价值为 (lenT)^2 / cT ,其中 lenT 表示 T 的长度,cT 表示 T 的最小循环节长度;

反之若 T 不存在循环节,则其价值为 0。 找出价值最大的子序列所具有的价值。

何谓循环节?若长度为 x 的字符串 A 具有循环节,则代表存在长度为 y 的字符串B,x % y = 0,且A恰好是由 x / y 个 B首尾相连组合而成的。

当 y 最小时,B称为 A 的最小循环节。

这可难倒了小蒟蒻,你能帮帮他吗?

输入格式

输入的第一行包括一个整数 n(1 ≤n≤ 10000),表示字符串的长度。

输入的第二行包括一个长为 n 的字符串,其组成元素只有’a’, ’b’, ’c’。

输出格式

输出包括一行一个元素ans,表示价值最大的子序列所具有的价值。

样例输入

3
abc

样例输出

3

  
  由抽屉原理易知,对于一个只由'a','b','c'组成的字符串,至少有一种字符的数量大于等于 n/3。那么不妨设'a'的数量大于等于 n/3,我们可以考虑去掉所有的'b'和'c',得到一个只由'a'组成的子序列。由公式 (lenT)^2 / cT 知,该子序列的价值大于等于 n^2/9。知道了该子序列的价值有什么用呢?根据题意,我们需要找的是最大价值的子序列,现在已经证明存在一个子序列的价值大于等于 n^2/9,那么目标子序列的价值一定也大于等于 n^/9。
  易知,最终得到的子序列的长度 lenT <= n。
  综合以上两个结论,我们可以得到 ans = (lenT)^2 / cT >= n^2 / 9 和 lenT <= n,由此便可推导出cT <= 9,易知以上两个式子不可同时取得等号,故cT < 9。

  有了cT < 9,这道题就变成了一道简单的dfs。首先我们生成所有长度小于9的简单循环节。(简单循环节即该字符串有且仅有唯一的循环节为其本身)
 1 for (int i = 1; i <= min(8, n); i++) {
 2     dfs(0, i);
 3 }
 4 
 5 void dfs(int step, int len) {
 6     if (step == len) {
 7         xhj[len] = '\0';
 8         if (issimple(len)) {
 9             check(len);
10         }
11     }
12     else {
13         for (int i = 0; i < 3; i++) {
14             xhj[step] = 'a' + i;
15             dfs(step+1, len);
16         }
17     }
18 }
生成所有长度小于9的字符串
 
 

  如何判断一个字符串是否是简单循环节呢?(为了方便,下文用len表示字符串长度)如果len=1,那么该字符串一定是简单循环节。如果len是2的倍数,若它不是简单循环节,那它一定是以2为周期的,我们只需将该字符串平分为两部分,检查前半部分与后半部分是否相等即可。如果len是3的倍数,若它不是简单循环节,那它一定是以3为周期的。同理,我们只需将该字符串平分为三部分,检查这三部分是否相等即可。若len = 5或7,若该字符串不是简单循环节,那它一定是由同一字符重复得到的,我们只需检查是否有两个字符不相等即可。
 1 bool issimple(int len) {
 2     if (len == 1) {
 3         return true;
 4     }
 5 
 6     if (len % 2 == 0) {
 7         for (int i = 0; i < len/2; i++) {
 8             if (xhj[i] != xhj[i+len/2]) {
 9                 return true;
10             }
11         }
12     }
13 
14     if (len % 3 == 0) {
15         for (int i = 0; i < len/3; i++) {
16             if (xhj[i] != xhj[i+len/3] || xhj[i] != xhj[i+len/3*2]) {
17                 return true;
18             }
19         }
20     }
21     
22 
23     if (xhj[0] != xhj[1]) {
24         return true;
25     }
26 
27     return false;
28 }
判断生成的字符串是否是简单循环节

 

  如果是简单循环节,我们再与题目所给字符串进行对比,抽取出由生成循环节循环产生的字符串,再计算出价值,输出其中最大的即可。

 1 void check(int len) {
 2     long long j = 0, counter = 0;
 3     for (long long i = 0; i < n; i++) {
 4         if (str[i] == xhj[j]) {
 5             counter++;
 6             j++;
 7             if (j >= len) {
 8                 j = 0;
 9             }
10         }
11     }
12 
13     counter = counter / len * len;
14     long long temp = counter * counter / len;
15     ans = (ans > temp) ? ans : temp;
16 }
计算子序列的价值

  

  如何找到我们生成的循环节对应的最长子序列呢?通过一一对比,我们很容易可以得到由循环节循环生成的子序列长度couter,但要注意这里的counter并不是真正的子序列长度,因为有可能找到的该段子序列末尾是一段不完整的子序列,所以我们需要通过counter = counter / len * len将其截断,这样就得到了子序列的真正长度。

 

 

 

你可能感兴趣的:(题解:小蒟蒻的子序列)