Codeforces 1296E2 String Coloring (hard version)(LIS,思维)
题目大意
给一个长度为\(n\)的字符串,要求将它染色然后按字典序交换排序(可以理解为冒泡),只有颜色不同的字符之间才能互相交换,问最少能用多少种颜色并且输出染色方案。
分析
只需要染一种颜色的情况
显然,像是abcdefghijklmn...这种根本不需要相互交换,所以只需要一种颜色。
需要染最多颜色的情况
像是nmlkjihgfedcba这种,每个字符都需要染一个颜色。可以想一下,a要在第一个位置,所以就要和前面所有的颜色不同,b要在第二个位置,需要交换的相邻位置也不能有相同颜色,而又因为先前需要和a交换,所以也必须和所有颜色不同...仔细想想发现每个字符都需要染上不同的颜色。那么最多要多少种颜色呢?一共26个小写字母所以最多就26种。
考虑更复杂的情况
如果是deabc呢?只需要de和abc颜色不同就行了。如果是edabc呢?首先abc需要移动到最前头,需要一种颜色,d要移动在e前头,需要一种颜色,因为e和另外4个字符都做过交换,所以也需要一种颜色,所以需要三种颜色。
所以说能得到什么结论呢?
通过上面的一些例子可以初步得到:对于每个非递减子序列来说,它们之间是不需要做交换的,比如说像是abc,aabc,azefbkc的子序列abc。所以每个非递减子序列只要染上相同的颜色就行了。我们真正需要做的是在非递减子序列中做一些交换,使得整个序列有序。可以想到一个比较简单的方法:先从序列中取出一个最长的非递减子序列,染上一种颜色;然后再取出一个最长的非递减子序列,再染上一种颜色。。。如果有些同学做过导弹防御的话是不是觉得有点眼熟?所以说和导弹防御一样,还有一种更优的解法。我们可以求一个最长的递减子序列,而需要染的颜色种类就是这个序列的长度,这个可以由狄尔沃斯(Dilworth)定理得出。说实话这个定理我也不会证明,只是主观感受上感觉它是对的。。。
还有一个问题,如何得到染色的方案呢?这个很简单,因为我们在求最长的递减子序列的时候,序列中的每个数都代表着以它结尾的一个非递减序列,所以只要在更新最长递减子序列的时候将它染上对应的颜色就行了。
具体实现
这题主要的难点就是思维的转换,别的其实挺简单的,而且因为字符一共就26种,所以稍微暴力一下的方法也可以过的。。。现在我们的工作就只有求最长递减子序列与染色。这里给出求最长递减子序列的两种方法,一种O(26n)一种O(nlog26)。染色的话,因为最长递减子序列中的每个位置的数都代表一个非递减子序列的末尾元素,所以就让末尾元素染上对应位置代表的颜色就行了。
代码1
const int maxn = 2e5+10;
char s[maxn], post[30];
int n, ans, color[maxn];
int main(void) {
cin >> n >> s;
for (int i = 0; i<n; ++i)
for (int j = 1; j<=26; ++j)
if (s[i]>=post[j]) {
post[j] = s[i];
color[i] = j;
ans = max(ans, j);
break;
}
cout << ans << endl;
for (int i = 0; i<n; ++i) printf(i==n-1 ? "%d\n":"%d ", color[i]);
return 0;
}
代码2
const int maxn = 2e5+10;
char s[maxn], post[30];
int n, tot=1, color[maxn];
int solve(char ch, int l, int r) {
while(l<r) {
int mid = (l+r)>>1;
if (post[mid]>ch) l = mid+1;
else r = mid;
}
return l;
}
int main(void) {
scanf("%d%s", &n, s);
for (int i = 0; i<n; ++i) {
if (post[tot]>s[i]) {
post[++tot] = s[i];
color[i] = tot;
}
else {
int p = solve(s[i], 1, tot);
post[p] = s[i];
color[i] = p;
}
}
printf("%d\n", tot);
for (int i = 0; i<n; ++i) printf(i==n-1 ? "%d\n":"%d ", color[i]);
return 0;
}