zoukankan      html  css  js  c++  java
  • @codeforces


    @description@

    给定一个字符串 s 与正整数 k。现在你需要进行恰好一次操作:

    (1)将 s 切割成最多 k 个子串。即令 s = t1 + t2 + ... + tm (1 <= m <= k)。
    (2)将其中的某些(不是全部) ti 翻转,得到 t1', t2', ... tm'。
    (3)重新拼合得到 s' = t1' + t2' + ... tm'。

    求字典序最小化的 s'。

    原题请戳我查看ouo。

    @solution@

    先考虑假如可以任意划分,而没有段数的限制的话该怎么做。
    注意到一个不翻转的子串可以拆解成若干个长度为 1 的翻转的子串,也就是说我们可以总默认所有子串都要翻转。
    考虑将原串 (S) 翻转得到 (S^r),则我们可以将操作等效地理解成将 (S^r) 划分成若干子串,然后从后往前取出这些串拼合得到 (S')
    要使 (S') 的字典序最小,不难联想到 lyndon 分解:(S^r) 的 lyndon 分解就是我们想要的答案(默认大家都会,不会可以百度.jpg)。

    假如加上 k 的限制,注意到当 k > 2 时我们依然可以贪心地取出最末尾的 lyndon 串。
    直观理解的话,大概就是这么做不会影响之后操作的合法性。
    注意到长度为 1 的 lyndon 串我们可以一起取出(对应不翻转的情况),长得一样的 lyndon 串我们也可以一起取出(一起翻转和分别翻转的结果一样)。
    (这里有一个处理的 trick:考虑 lyndon 分解的过程,在 lyndon 分解的时候我们就可以把长得一样的串处理出来)

    接下来考虑 k <= 2 的情况。k = 1 没什么话说,主要说一下 k = 2。

    分几种情况:

    (1)划分线前后的串都不翻转。情况唯一。

    (2)划分线前的串不翻转,划分线后的串翻转。发现得到的 (S') = (S) 的一个前缀 + (S^r) 的一个前缀。
    比较字典序时可以用求 lcp 的方法比较。注意到只需要求某个子串和 (S^r) 的 lcp,所以把 (S^r #S) 拿去建 (Z-algorithm)(扩展 kmp)就 OK。

    (3)划分线前的串翻转。此时划分线前的串是 (S^r) 的一个后缀 (T)
    我们先要让划分线前字典序最小,至少要满足 (T) 的所有后缀要么字典序严格大于 (T),要么是 (T) 的前缀。
    考虑依然对 (S^r) 进行 lyndon 分解,则 (T) 应该是末尾几个完整的 lyndon 串拼合,不然矛盾。
    经过分析,满足上述条件的情况下,(T) 最多包含两种不同 lyndon 串。同时为了字典序最小,假如 (T) 包含了某一种 lyndon 串,就应包含所有与它相同的串。
    再讨论后面要不要翻转,一共 4 种情况,暴力求出来比较字典序即可。

    @accepted code@

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int MAXN = 5000000;
    
    void solve1(char *S) {
    	int lenS = strlen(S);
    	for(int i=0,j=lenS-1;i<j;i++,j--)
    		if( S[i] < S[j] ) break;
    		else if( S[i] > S[j] ) {
    			for(i=0,j=lenS-1;i<j;i++,j--)
    				swap(S[i], S[j]);
    			break;
    		}
    	puts(S);
    }
    
    void lyndon(char *S, int *f, int lenS) {
    	int cnt = 0;
    	for(int i=0;i<lenS;i++) f[i] = 0;
    	for(int i=0;i<lenS;) {
    		int j = i, k = i + 1;
    		while( k < lenS && S[j] <= S[k] )
    			j = (S[j] == S[k] ? j + 1 : i), k++;
    		int t = k - j; cnt++;
    		while( i + t - 1 < k )
    			f[i + t - 1] = cnt, i += t;
    	}
    }
    
    void algorithmZ(char *S, int *f, int lenS) {
    	f[0] = lenS; int mx = 0, ps = 0;
    	for(int i=1;i<lenS;i++) {
    		f[i] = (i <= mx ? min(mx - i + 1, f[i - ps]) : 0);
    		while( S[f[i]] == S[i+f[i]] ) f[i]++;
    		if( i + f[i] - 1 > mx ) mx = i + f[i] - 1, ps = i;
    	}
    }
    
    char ans[MAXN + 5], T[2*MAXN + 5]; int f[2*MAXN + 5];
    int cmp(int l, int r) {
    	int p = f[l];
    	if( p >= r - l + 1 ) return 0;
    	else return (T[l + p] < T[p] ? -1 : 1);
    }// (s[l, r] == s[0, r-l] ? 0 : (s[l, r] < s[0, r-l] ? -1 : 1))
    bool cmp2(char *S, char *T, int n) {
    	for(int i=0;i<n;i++) {
    		if( S[i] < T[i] ) return true;
    		else if( S[i] > T[i] ) return false;
    	}
    	return true;
    }// S < T
    void update(char *T, int lenS) {
    	if( cmp2(T, ans, lenS) ) for(int i=0;i<lenS;i++) ans[i] = T[i];
    }
    int get(int x) {
    	int p; for(p = x - 1; p >= 0 && f[p] == 0; p--);
    	return p;
    }
    
    void update2(char *S, int x, int lenS) {
    	for(int i=0;i<x;i++) T[i] = S[x-i-1];
    	for(int i=x;i<lenS;i++) T[i] = S[i];
    	update(T, lenS);
    	for(int i=x,j=lenS-1;i<j;i++,j--) swap(T[i], T[j]);
    	update(T, lenS);
    }
    
    void solve2(char *S) {
    	int lenS = strlen(S);
    	for(int i=0;i<lenS;i++) ans[i] = S[i];
    	
    	for(int i=0;i<lenS;i++) T[lenS-i-1] = S[i], T[lenS+i+1] = S[i];
    	algorithmZ(T, f, 2*lenS+1);
    	int pos = 0;
    	for(int i=1;i<lenS-1;i++) {
    		int t = cmp(lenS + 1 + pos + 1, lenS + 1 + i);
    		if( t == 0 ) {
    			if( cmp(i - pos, lenS - pos - 2) == 1 ) pos = i;
    		}
    		else if( t == -1 ) pos = i;
    	}
    	for(int i=0;i<lenS;i++) T[i] = S[i];
    	for(int i=pos+1,j=lenS-1;i<j;i++,j--) swap(T[i], T[j]);
    	update(T, lenS);
    	
    	for(int i=0;i<lenS;i++) T[lenS-i-1] = S[i];
    	lyndon(T, f, lenS);
    	int tmp = get(lenS - 1);
    	while( tmp >= 0 && f[tmp] == f[lenS - 1] ) tmp = get(tmp);
    	update2(S, lenS - tmp - 1, lenS);
    	if( tmp >= 0 ) {
    		int p = get(tmp);
    		while( p >= 0 && f[p] == f[tmp] ) p = get(p);
    		update2(S, lenS - p - 1, lenS);
    	}
    	puts(ans);
    }
    
    void print(int l, int r) {
    	for(int i=l;i<=r;i++)
    		putchar(T[i]);
    }
    
    char S[MAXN + 5];
    int main() {
    	int k; scanf("%s%d", S, &k);
    	if( k == 1 ) solve1(S);
    	else {
    		int lenS = strlen(S);
    		for(int i=0;i<lenS;i++) T[lenS-i-1] = S[i];
    		lyndon(T, f, lenS);
    		int lst = lenS - 1, len = 0, tmp = 0;
    		while( k >= 2 && lst >= 0 ) {
    			int p = get(lst);
    			if( !(len == 1 && lst - p == 1) && !(f[lst] == tmp)  ) {
    				if( k == 2 ) break;
    				k--;
    			}
    			print(p + 1, lst);
    			len = lst - p, tmp = f[lst], lst = p;
    		}
    		if( lst >= 0 ) solve2(S + (lenS - lst - 1));
    	}
    }
    

    @details@

    直觉猜想和 lyndon 分解有关,然而想不到怎么处理 k = 2 的情况。。。

    感觉考察了两个冷门的线性字符串算法。。。而且两个都是初见。。。

    另外,正确性的证明什么的。。。不是很擅长不过直观理解起来都对所以就没写了啦啦啦。

  • 相关阅读:
    30 字符编码
    Xilinx Vivado的使用详细介绍(3):使用IP核
    Xilinx Vivado的使用详细介绍(1):创建工程、编写代码、行为仿真
    Vivado SDK ,调用math.h函数的时候出现 undefined reference to `xxx' ,解决方案
    Xilinx Vivado的使用详细介绍(4):Zedboard+vivado之流水灯(加SDK)
    xilinx Vivado的使用详细介绍(2):创建工程、添加文件、综合、实现、管脚约束、产生比特流文件、烧写程序、硬件验证
    no.4
    no.5
    关于贪心算法
    关于三角形把平面分成几块的问题
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12148862.html
Copyright © 2011-2022 走看看