• @bzoj



    @description@

    给定一个长度为 n 的仅包含'B'、'C'、'S'三种字符的字符串,请找到最长的一段连续子串,使得这一段要么只有一种字符,要么有多种字符,但是没有任意两种字符出现次数相同。

    input
    第一行包含一个正整数 n(1<=n<=1000000),表示字符串的长度。
    第二行一个长度为 n 的字符串。

    output
    包含一行一个正整数,即最长的满足条件的子串的长度。

    sample input
    9
    CBBSSBCSC
    sample output
    6

    @solution@

    这篇博客内有一个很神仙的结论:

    最优解 [l, r] 一定满足 (l in [1, 3])(r in [n-2, n])

    至于证明,那位博主略去了。。。

    我来尝试证明一下吧:

    性质 <1>: 如果 [l, r] 满足没有任意两种字符出现次数相同,则一定能找到合法区间 [l', r'] 满足 (l' in [l-3,l], r' in [r,r+3]),且 r - l + 1< r' - l' + 1。
    使用反证法。不失一般性,假设 [l, r] 内三种字符的出现次数分别为 (a, b, c) 且满足 a < b < c。

    分类讨论 l-1 是什么字符:
    (1) [l-1, r] 对应 (a, b, c+1),始终满足 a < b < c + 1。
    (2) [l-1, r] 对应 (a, b+1, c),若不满足 a < b + 1 < c,则有 b + 1 = c 成立。
    (2) [l-1, r] 对应 (a+1, b, c),若不满足 a + 1 < b < c,则有 a + 1 = b 成立。

    类似地对 r+1 进行分类,可以得到一个初步的结论:如果性质不成立,那么 (a, b, c) 至少要满足 a + 2 = b + 1 = c。

    剩下的证明因为我很懒再加上我也不是专业的采用 dfs 枚举所有可能。用于验证的程序如下:

    #include<cstdio>
    int a[6], s[3];
    void dfs(int x) {
    	if( x == 6 ) {
    		for(int i=0;i<=3;i++)
    			for(int j=2;j<=5;j++) {
    				if( i <= j ) {
    					s[0] = -1, s[1] = 0, s[2] = 1;
    					for(int k=i;k<=j;k++)
    						s[a[k]]++;
    					if( s[0] != s[1] && s[0] != s[2] && s[1] != s[2] )
    						return ;
    				}
    			}
    		puts("error");
    		return ;
    	}
    	a[x] = 0; dfs(x + 1);
    	a[x] = 1; dfs(x + 1);
    	a[x] = 2; dfs(x + 1);
    }
    int main() {
    	dfs(0);
    }
    

    性质 <2>:如果 [l, r] 满足只有一种字符且 r - l + 1 > 1,则一定能找到合法区间 [l', r'] 满足 l - 1 = l' 或 r + 1 = r'。
    对于区间 [l-1, r],它要么是形如 (0, 0, r - l + 2),要么是形如 (0, 1, r - l + 1)。
    对于区间 [l, r+1] 同理。

    有了这两个性质就可以证明我们的结论了。
    假如最优解 [l, r] 不满足结论,则有 l > 3, r < n - 2。由性质 <1> 或性质 <2> 可知可以通过移动它的左右端点使得区间长度变大。故它一定不是最优解。
    有一些不严谨的地方,就是如果 r - l + 1 = 1 的时候。这种情况特殊讨论一下也成立。

    时间复杂度 O(n)。

    @accepted code@

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int MAXN = 1000000 + 5;
    inline int fun(char ch) {
    	if( ch == 'B' ) return 0;
    	if( ch == 'C' ) return 1;
    	if( ch == 'S' ) return 2;
    }
    int n, sum[3][MAXN];
    int f(int x, int l, int r) {
    	return sum[x][r] - sum[x][l - 1];
    }
    bool check(int l, int r) {
    	if( f(0, l, r) != f(1, l, r) && f(0, l, r) != f(2, l, r) && f(1, l, r) != f(2, l, r) ) return true;
    	if( (!f(0, l, r) && !f(1, l, r)) || (!f(0, l, r) && !f(2, l, r)) || (!f(1, l, r) && !f(2, l, r)) ) return true;
    	return false;
    }
    char s[MAXN];
    int main() {
    	int n, ans = 1;
    	scanf("%d%s", &n, s + 1);
    	for(int i=1;i<=n;i++) {
    		sum[0][i] = sum[0][i-1];
    		sum[1][i] = sum[1][i-1];
    		sum[2][i] = sum[2][i-1];
    		sum[fun(s[i])][i]++;
    	}
    	for(int i=1;i<=n;i++) {
    		if( 1 <= i && check(1, i) ) ans = max(ans, i - 0);
    		if( 2 <= i && check(2, i) ) ans = max(ans, i - 1);
    		if( 3 <= i && check(3, i) ) ans = max(ans, i - 2);
    		if( i <= n - 0 && check(i, n - 0) ) ans = max(ans, n - i + 1);
    		if( i <= n - 1 && check(i, n - 1) ) ans = max(ans, n - i);
    		if( i <= n - 2 && check(i, n - 2) ) ans = max(ans, n - i - 1);
    	}
    	printf("%d
    ", ans);
    }
    

    @details@

    当然这种神仙结论我肯定是想不到的。

    所以,我选用的是另外一种方法:
    只含一种字符的区间显然可以随便怎么搞。

    定义 sum[i] 表示字符 i 的前缀和。如果区间 [l + 1, r] 合法,则一定有:
    sum[0][r] - sum[0][l] ≠ sum[1][r] - sum[1][l]
    sum[0][r] - sum[0][l] ≠ sum[2][r] - sum[2][l]
    sum[1][r] - sum[1][l] ≠ sum[2][r] - sum[2][l]

    变一下形:
    sum[0][r] - sum[1][r] ≠ sum[0][l] - sum[1][l]
    sum[0][r] - sum[2][r] ≠ sum[0][l] - sum[2][l]
    sum[1][r] - sum[2][r] ≠ sum[1][l] - sum[2][l]

    枚举 r,相当于找到一个最小的 l 满足上述条件。
    先判断它是不是三个前缀和都不一样,如果是就有 l = 0。
    接下来,对于某一个 l,不满足上述条件的 r 其实很少。我们大力分类讨论即可。
    时间复杂度还是 O(n) 的,但是代码复杂度……

  • 相关阅读:
    spring mongodb查询
    spring mongodb分页,动态条件、字段查询
    js导航下拉菜单
    spring mongodb增删改查操作
    组件
    vue的基本指令
    远程连接MongoDB数据库
    webpack工具、Vue、react模块化
    layui
    anaconda使用,jupyter notebook的使用方法
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10364777.html
Copyright © 2020-2023  润新知