Codeforces Round #791 (Div. 2) E, F 题题解 (2400,2600)
\(\mathbb{E}\)
给定一个长度为 \(n\) 的字符串 \(s\),由前 \(k\) 个小写字母和 \(\texttt{?}\) 组成。
有 \(q\) 次询问,给出一个前 \(k\) 个小写字母的子集 \(t\),问将 \(s\) 中的所有 \(\texttt{?}\) 替换成 \(t\) 中的任意一个字符的所有方案,\(s\) 包含的所有可以本质相同的回文字串的个数的和,答案对 \(998244353\) 取模。
\(1\le n\le 10^3,1\le q\le 2\cdot 10^5,1\le k\le 17\)。
随便做。
考虑回文串中的两个对称位置 \(i,j\),有四种情况:
- \(s_i=s_j=\texttt{?}\):贡献为 \(|t|\)。
- \(s_i=\texttt{?},s_j\neq \texttt{?}\):贡献为 \([s_j\in t]\)。
- \(s_i\neq \texttt{?},s_j=\texttt{?}\):贡献为 \([s_i\in t]\)。
- \(s_i\neq \texttt{?},s_j\neq \texttt{?}\):贡献为 \([s_i=s_j]\)。
那么区间 \(\text{dp}\) 出每个区间的贡献,每个贡献形如 \(|t|^{p}\times [S\in t]\),于是高维前缀和即可。
总时间复杂度 \(O(n^2+2^{17}\times 17^{2})\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1005,mod=998244353;
int n,S,q,sum[N],P[18][N],pw[N][N],zero[N][N],sta[N][N],f[1<<17][18];
char s[N],t[N];
int main(){
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;++i)sum[i]=sum[i-1]+(s[i]=='?');
for(int L=1;L<=17;++L){
P[L][0]=1;
for(int i=1;i<=n;++i)
P[L][i]=(ll)P[L][i-1]*L%mod;
}
for(int t=1;t<=n;++t)
for(int i=1;i<=n-t+1;++i){
int j=i+t-1;
zero[i][j]=zero[i+1][j-1];
pw[i][j]=pw[i+1][j-1];
sta[i][j]=sta[i+1][j-1];
if(s[i]=='?'&&s[j]=='?')++pw[i][j];
else if(s[i]!='?'&&s[j]!='?')zero[i][j]|=s[i]!=s[j];
else sta[i][j]|=(s[i]!='?'?(1<<s[i]-'a'):(1<<s[j]-'a'));
if(!zero[i][j])
for(int L=1;L<=17;++L)
(f[sta[i][j]][L]+=P[L][pw[i][j]+sum[n]-(sum[j]-sum[i-1])])%=mod;
}
for(int i=0;i<17;++i)
for(int j=0;j<1<<17;++j)if(j>>i&1)
for(int k=1;k<=17;++k)(f[j][k]+=f[j^(1<<i)][k])%=mod;
for(scanf("%d",&q);q--;){
scanf("%s",t+1),S=0;
for(int i=1;i<=strlen(t+1);++i)S|=1<<t[i]-'a';
printf("%d\n",f[S][strlen(t+1)]);
}
return 0;
}
\(\mathbb{F}\)
给出正整数 \(n\),所有不足 \(n\) 位(十进制)的数用前导零补充。
给出 \(m\) 组无序数对 \((u_i,v_i)\),若一个数字的相邻两位数 \(x,y\) 满足 \((x,y)\) 存在于这 \(m\) 组数对中,则可以交换 \(x,y\) 的位置。若 \(A\) 可以通过若干次(包含零次)交换得到 \(B\),则认为 \(A\) 和 \(B\) 是等价的。
求出最大整数 \(k\),使得存在一组非负整数 \(x_1,x_2,\ldots,x_k(0\leq x_i<10^n)\) 满足对于任意 \(1\leq i<j\leq k\),\(x_i\) 与 \(x_j\) 不等价。
更简单,不知道为何能放到 \(\text{F}\) 的位置,标签竟有 \(2600\)。
考虑 \(10^n\) 个数中形成若干等价类,要求的就是等价类的数量。对于一个等价类,考虑用其字典序最小的元素描述它。
如何保证字典序最小呢?字典序最小等价于不能再通过操作使得字典序更小,形式化地,设字典序最小的数为 \(\overline{d_1d_2\ldots d_n}_{10}\),不能找出 \(d_j\) 使得 \(d_j\) 与 \(d_i,d_{i+1},\ldots, d_{j-1}\) 都可以交换并且 \(d_j<d_i\)。
于是可以从低位到高位状压 \(\text{dp}\),同时维护状态 \(s\),表示能从后面移动到当前位置的数字集合,每次重新求 \(s\) 时间复杂度不对,那么进行预处理,\(g_{s,i}\) 表示现在的集合为 \(s\),在前面加入 \(i\) 后新的 \(s\),然后进行 \(\text{dp}\),转移时不能使得后面能交换到当前位置的数字比当前填的数字小。
总时间复杂度 \(O(n\times 2^{10}\times 10)\)。
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int n,m,ans,f[50005][1024],g[1024][10],G[10][10];
int main(){
scanf("%d%d",&n,&m),f[0][0]=1;
for(int i=1,x,y;i<=m;++i)scanf("%d%d",&x,&y),G[x][y]=G[y][x]=1;
for(int s=0;s<1024;++s)
for(int i=0;i<=9;++i){
g[s][i]=1<<i;
for(int t=0;t<=9;++t)
if(G[i][t]&&(s>>t&1))
if(i<t){g[s][i]=-1;break;}
else g[s][i]|=1<<t;
}
for(int i=0;i<n;++i)
for(int s=0;s<1024;++s)if(f[i][s])
for(int j=0;j<10;++j)if(~g[s][j])
(f[i+1][g[s][j]]+=f[i][s])%=mod;
for(int s=0;s<1024;++s)(ans+=f[n][s])%=mod;
return printf("%d\n",ans),0;
}