zoukankan      html  css  js  c++  java
  • @AGC037


    @description@

    给定一个长度为 N 且只包含小写字母的字符串 S ,你可以执行 k 次操作,每次操作你可以:

    (1)将 S 翻转得到 T,将 S 与 T 拼接得到 U。
    (2)从 U 中取出长度为 N 的子串 S',替换当前 S 进行下一轮迭代。

    你需要求出 k 次操作后字典序最小的 S。

    Constraints
    1≤N≤5000, 1≤K≤10^9, |S|=N。保证 S 只包含小写字母。

    Input
    输入格式如下:
    N K
    S
    Output
    输出 K 次操作后字典序最小的字符串。

    Sample Input 1
    5 1
    bacba
    Sample Output 1
    aabca

    S=bacba, T=abcab, U=bacbaabcab, S′=aabca。

    Sample Input 2
    10 2
    bbaabbbaab
    Sample Output 2
    aaaabbaabb

    @solution@

    是我太傻。。。

    k 这么大,是否一定操作过后字符串就恒定不变呢?
    那么我们不妨大胆猜测,一定操作过后,字符串变成只含有最小字母 mn 的字符串("aaa...a" 型)。

    最少多少次呢?假如我们 S 有一个以 mn 结尾的长度为 p 的连续段,则 U 的中间就有 2*p 的连续段。
    假如我们选择以这 2*p 个 mn 为结尾的 S',我们又可以得到 4*p 个 mn 结尾的 S'' 等。即:mn 的数量可以呈指数级增长。
    同时,只有末尾的连续段才会有这个性质,不在末尾的甚至不会与另一个字符串 T 产生关系。
    当 k > log|S| 时,我们可以直接输出答案了。剩下的只需要讨论 k <= log|S| 的时候

    根据字典序的定义,我们想让前面的字符尽可能地字典序小。
    而最后一次我们可以让 mn 直接放在字符串的开头,这样 mn 越多显然越优秀。

    于是我们的贪心策略就是:枚举第一次操作选择的字符串,然后按照上面所说,将末尾的连续段翻倍再翻倍。
    最后一次再把连续段作为开头截取下来,得到最终的字符串。

    你以为要暴力模拟过程?NONONO!
    我们在最后一次之前,末尾连续段之前的那些 “冗余” 部分的相对顺序是不会变的,保持着在原串中的顺序。
    而最后一次只是把那些 “冗余” 部分翻转一下,所以可以直接得到最终字符串。

    因为更新答案是暴力比较的,所以时间复杂度是 O(n^2) 的。

    @accepted code@

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int MAXN = 5000;
    char str[2*MAXN + 5], mn;
    char ans[MAXN + 5];
    int N, K;
    bool check(char *S) {
    	for(int i=1;i<=N;i++)
    		if( ans[i] != S[i] )
    			return ans[i] > S[i];
    	return false;
    }
    void update(char *S) {
    	for(int i=1;i<=N;i++)
    		ans[i] = S[i];
    }
    int pw[35];
    char S[MAXN + 5];
    int main() {
    	pw[0] = 0, pw[1] = 2;
    	for(int i=2;i<=30;i++) pw[i] = 2*pw[i-1];
    	scanf("%d%d%s", &N, &K, str + 1);
    	mn = 'z';
    	for(int i=1;i<=N;i++)
    		mn = min(mn, str[i]), ans[i] = 'z';
    	if( K >= 30 ) {
    		for(int i=1;i<=N;i++)
    			putchar(mn);
    	}
    	else {
    		for(int i=1;i<=N;i++)
    			str[2*N - i + 1] = str[i];
    		int cnt = 0;
    		for(int i=1;i<N;i++)
    			cnt = (str[i] == str[i-1]) ? cnt + 1 : 1;
    		for(int i=N;i<=2*N;i++) {
    			cnt = (str[i] == str[i-1]) ? cnt + 1 : 1;
    			if( pw[K - 1] <= N/cnt ) {
    				for(int j=1;j<=pw[K-1]*cnt;j++)
    					S[j] = str[i];
    				int k = i - cnt;
    				if( K == 1 ) k = i;
    				for(int j=pw[K-1]*cnt+1;j<=N;j++)
    					S[j] = str[k--];
    			}
    			else {
    				for(int j=1;j<=N;j++)
    					S[j] = str[i];
    			}
    			if( check(S) ) update(S);
    		}
    		puts(ans + 1);
    	}
    }
    

    @details@

    可能是自己还不大擅长贪心。。。

    贪心太难了。。。

  • 相关阅读:
    springmvc入门详解
    getClass 与getSimpleName
    mybati的存储过程
    mybatis与spring的整合
    mybatis分页插件以及懒加载
    mybatis知识总结
    【Java面试题】30 子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
    【Java面试题】29 设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
    【Java面试题】28 简述synchronized和java.util.concurrent.locks.Lock的异同 ?
    【Java面试题】27 多线程笔试面试概念问答
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11681596.html
Copyright © 2011-2022 走看看