「Wdcfr-1」CONsecutive and CONcat (easy ver.) 题解
原来的官方题解比较有误导性,包括但不限于公式中代表变量的字母写错,计算方式很容易让人理解错意思导致边界条件处理不清楚。这里提供一份较为完整且附带参考代码的题解。
考虑把贡献分成两部分:
一部分为单个字符串内部贡献,即对于单个字符串内所有长度为
比较复杂的另一部分是对于两个字符串,前者后缀拼接后者前缀产生的贡献(这里的后缀内的所有字符、缀前内的所有字符都相同)。具体地,因为
所以我们只能考虑子串跨越两个字符串的情况,及它的真前缀在前者的后缀内,它的真后缀在后者的前缀内。于是我们可以的出后缀长度为
当然要注意误把同一个字符串“自己和自己拼接”加入贡献的情况,把这部分去掉即可。
把两部分合起来就是最终答案。
放代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int p=998244353;
main(){
ios::sync_with_stdio(false);
int n,m,k,r=0,F=1,F2=1; cin>>n>>m>>k;
for(int i=2;i<=n;i++)(F*=i)%=p;
for(int i=2;i<n;i++)(F2*=i)%=p;
// 预处理阶乘,F = n!,F2 = (n - 1)!
vector<string> a(n);
vector<array<int,26> > b(m),s(m);
// b[i][j] 统计具有长度为 i、字符全部为 c 的极长前缀的字符串个数
// s[i][j] 类似,只是把前缀换成后缀
vector e(m,vector<array<int,26> >(m));
for(auto &i:a){
cin>>i; int t=0,l=0;
for(int j=0;j<m;j++)
if(!j||i[j]==i[j-1])t++; // 与上一个字符相同
else{
if(t>=k)(r+=F*(t-k+1)%p)%=p; // 第一部分贡献
t=1; // 将答案清空,计算下一段
}
if(t>=k)(r+=F*(t-k+1)%p)%=p; // 最后还有一段
for(int j=0;j<m&&i[j]==i[0];j++)l++;
b[l][i[0]-97]++,s[t][i[m-1]-97]++;
// l 为极长的、字符相同的前缀长度
// 利用剩余的 t 得到极长的、字符相同的后缀长度
// 统计特定字符的前缀、后缀长度确定时的字符串个数
if(i[0]==i[m-1])e[l][t][i[0]-97]++;
// 自己和自己拼接的情况
}
auto f=[&](int i,int j,int k){
return min(i,i+j-k+1)-max(1ll,i-k+2)+1;
}; // 计算拼接时“跨越”字符串的子串数
// i 为后缀长度,j 为前缀长度
for(int c=0;c<26;c++)
for(int i=1;i<m;i++)
for(int j=max(k-i,1ll);j<m;j++)
(r+=(b[j][c]*s[i][c]%p-e[j][i][c]+p)%p*f(i,j,k)%p*F2%p)%=p;
// 统计所有方案数
cout<<r<<endl;
return 0;
}