[FZYZOJ6693] 字符串变换
题目链接:
来源:The 2021 ICPC Asia Shenyang Regional Contest G - Encoded String II
题目描述
定义字符集为 \(\Sigma\),长度为 \(n\) 的字符串 \(S\) 的一个变换函数 \(F\):
对于字符 \(c \in \Sigma\),\(F(S,c)\) 的值等于 \(S\) 中最后一个 \(c\) 字符之后,不同的字符种数加上 \(\texttt{'a'}\)(这些都是 ASCII 意义下的)。
定义字符串 \(S\) 的变换串,即在同一时刻把字符串内每一个字符 \(S_i\) 换成对应的字符 \(F(S,S_i)\),得到的字符串。
例如串 \(\texttt{cbabca}\) 的变换串就是 \(\texttt{bcacba}\)。
请问,这个字符串 \(S\) 的所有 \(2^{n}-1\) 个非空子序列中,字典序最大的变换串是哪一个串?
说明/提示
对于所有数据,保证 \(1 \le n \le 10^5\),\(|\Sigma| \le 22\),即对于本题字符集只会有前 \(22\) 种小写字母。
子任务特殊限制如下:
子任务 | 特殊限制 | 分值 |
---|---|---|
\(1\) | \(n \le 20\) | \(15\) |
\(2\) | 字符集为 \(\{\texttt{'a'},\texttt{'b'}\}\) | \(20\) |
\(3\) | 字符集只有前 \(5\) 种小写字母 | \(25\) |
\(4\) | 字符集只有前 \(20\) 种小写字母 | \(30\) |
\(5\) | 无 | \(10\) |
解题思路
手玩几组样例,发现一些性质:
- 答案串中的字母的字典序一定是不升的。
- 答案串中一定会出现字母表中前 \(|\Sigma|\) 的字母。
这些性质的证明显然。
由于要字典序最大,因此答案串中最大的字母一定要最多,且要保证 \(\Sigma\) 中每种字母都至少出现一次(不然字母种数不够,答案串字典序会变小)。
因此,在这个前提下,答案串字典序最大等价于从前往后每次选择一种字符作为答案串的某段字符,且每步都要选到最多的字符。
这么说有点抽象,那就来看一组样例:对于字符串 \(\texttt{acbccab}\):
情况一:我们从前往后可以依次选择字符 \(\texttt{accc}\),但这样选并没有每种字符都出现过,因此答案串 \(\texttt{baaa}\) 不是最优选择。
情况二:子序列 \(\texttt{acbcc}\) 由于字符 \(\texttt{b}\) 混入了字符 \(\texttt{c}\) 的段中,导致答案串 \(\texttt{cabaa}\) 不满足字典序不升的要求,不是最优选择。
情况三:同样,我们可以从前往后依次选择字符 \(\texttt{acccb}\) 或 \(\texttt{cccab}\),但前者在选择第一种字符时并没有取到最多,因此两者对应的答案串 \(\texttt{cbbba}\) 和 \(\texttt{cccba}\),后者字典序更大。
考虑到字符集较小,因此可以状压 DP。记以下数组:
\(last_{i,j}\):字符 \(i\) 在位置 \(j\)(含)之前最后出现的位置。
\(sum_{i,j}\):字符 \(i\) 在位置 \(j\)(含)之前出现多少次(用于求某段区间某种字符的出现次数)。
\(g_S\):从前往后贪心地选了 \(S\) 中的字符,为使所有字符都出现,最远选到哪里(即满足后缀 \([pos,n]\) 包含 \(\Sigma-S\) 中所有字符的最大 \(pos\) 减去 \(1\))。
\(p_S\):从前往后贪心地选 \(S\) 的字符,在满足条件和当下最优的情况下,最近选到哪里。
\(f_S\):从前往后贪心地选了 \(S\) 中的字符,在满足所有要求的前提下,最后一种字符最多出现多少次。
考虑转移。在预处理好 \(last\),\(sum\),\(g\) 后,我们按照 \(|S|\) 从小到大刷表法转移 \(f_S\)(相当于从前往后每次选一种字符)。为了保证每种字符选到最多,在 \(|S|\) 相等的情况下,只能用最优的 \(f_S\) 转移。考虑在 \(S\) 中加入 \(c\),设可选范围为 \([l,r]\),为了不出现情况二中后面一段字符混入前一段字符的情况,\([l,r]\) 中除 \(c\) 外的字符都要删去,\(l\) 应等于 \(p_S+1\)。而为了保证每种字符都出现一次,\(r\) 应等于 \(g_{S+c}\)。所以,转移方程为
\(sum_{c,r}-sum_{c,l-1}\rightarrow f_{S+c}\)。同时更新 \(p_{S+c}\) 的值。
最终答案为 \(\displaystyle\max_{|S|=1}f_S\) 个字符 \(\texttt{a}+|\Sigma|-1\) 连上 \(\displaystyle\max_{|S|=2}f_S\) 个字符 \(\texttt{a}+|\Sigma|-2\) 连上……连上 \(\displaystyle\max_{|S|=|\Sigma|}f_S\) 个字符 \(\texttt{a}\),时间复杂度 \(O(|\Sigma|2^{|\Sigma|})\)。
参考代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,lastmax;
char s[100005];
bool vis[30];
int last[30][100005],sum[30][100005],sz[9000005],g[9000005],f[9000005],p[9000005];
int main()
{
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++)
{
vis[s[i]-'a']=1;
for(int j=0;j<23;j++)
{
if(s[i]==j+'a') last[j][i]=i,sum[j][i]=sum[j][i-1]+1;
else last[j][i]=last[j][i-1],sum[j][i]=sum[j][i-1];
}
}
int full=0;
for(int i=0;i<23;i++) if(vis[i]) full|=1<<i,m++;
for(int i=0;i<(1<<23);i++) sz[i]=__builtin_popcount(i);
for(int i=0;i<(1<<23);i++)
{
if((i&full)!=i) continue;
g[i]=n+1;
for(int j=0;j<23;j++)
{
if(vis[j]&&((i>>j)&1)==0)
{
g[i]=min(g[i],last[j][n]);
}
}
g[i]--;
}
for(int i=0;i<m;i++)
{
int maxn=0;
for(int j=0;j<(1<<23);j++)
{
if((j&full)!=j||sz[j]!=i||f[j]!=lastmax) continue;
for(int k=0;k<23;k++)
{
if(vis[k]&&((j>>k)&1)==0)
{
int c=sum[k][g[j|(1<<k)]]-sum[k][p[j]];
maxn=max(maxn,c);
if(c>f[j|(1<<k)])
{
f[j|(1<<k)]=c;
p[j|(1<<k)]=last[k][g[j|(1<<k)]];
}
else if(c==f[j|(1<<k)])
{
p[j|(1<<k)]=min(p[j|(1<<k)],last[k][g[j|(1<<k)]]);
}
}
}
}
lastmax=maxn;
while(maxn--) putchar(m-i-1+'a');
}
return 0;
}