题解-CF578D LCS Again
题面
给定 \(n,m\) 和一个长度为 \(n\) 的字符串 \(S\)。求有多少不同的 \(T\),使得它和 \(S\) 的最长公共子序列长度恰好为 \(n-1\)。所有字符都在 \([0,m)\) 之间。
数据范围:\(1\le n\le 10^5\),\(2\le m\le 26\)。
题解
题外话:这题小萌新想想写写搞了一整天的 dp
做法,已经能过几乎所有小数据,最终没有做出来,如果有大佬用 dp
做法过了,千万要在评论区里 D
小萌新一顿,并教教小萌新 dp
怎么做。
大致是设 \(f[i][j]:j\in[0,4)\) 表示第 \(i\) 个字符的状态为 \(j\) 的方案数。\(j=0\) 表示两个字符串的前缀 \(\rm lcs\) 已经是 \(i-1\) 了而且 \(T[i]=S[i]\);\(j=1\) 表示 \(T[i]=S[i+1]\);\(j=2\) 表示 \(T[i]=S[i-1]\);\(j=3\) 表示 \(T[i]\neq S[i]\),但是这个字符的命运待定。
正解做法是推理(或许应该叫乱搞?),个人感觉思维难度较大。
注意,如果下文内容使您恶心想吐,请您静心阅读并思考。
先转化题目:从 \(S\) 中删除并插入一个字符,有多少种不同且 \(\neq S\) 的字符串。
首先很明显,对于同一段的字符串,删除它们后插入形成的字符串集合是相等的。
比如
abbc
删第一个b
能补成的字符串集和删第二个b
的。
所以只需要在每段相同字符的第一个统计删除贡献即可。
考虑删除一个字符后通过插入可以得到的字符串集合的大小。
比如下面字符串删掉中间的 c
:
abcde
ab de
删除 c
后,原位不能填 c
,所以这一位有 \(m-1\) 种填法。
b
左边不能填 b
,要不然和 c
空位上填 b
效果一样,也是 \(m-1\) 种填法。
a
左边不能填 a
,要不然和 b
左边填 a
效果一样,也是 \(m-1\) 种填法。
右边的 d
和 e
同理。
然后因为这样任意两种插法在同个地方插入必然互不相同,而在不同地方插入又至少有一个位置不同。
所以删除一个字符后通过插入可以得到的字符串集合的大小为 \(n(m-1)\)。
最后一个问题是把不同相同段的第一个字符删除后形成的字符串集合合并。
考虑删除两个字符,在怎样的情况下造成重复。
-
两个字符相同且在不同的段中。以下三种情况易证没有重复:
- 插入字符不同。
- 插入字符相同且不等于删除字符。
- 插入字符相同且都等于删除字符。
-
两个字符不同。那么如果重复,每个字符数量必然相等,所以删
a
的必须插a
,删b
的必须插b
。
比如字符串 b******a
,那么把 a
扔到左端变成 ab******
。
因为扔到别的地方根据 \(m-1\) 原则
a
右边也 \(\neq\)a
,所以这会变成同一个模型。
设 *
\(_i\) 表示 *
串的第 \(i\) 个字符,不妨设上面 *
串的长度为 \(6\)。
考虑怎么扔 b
。首先由于只在每段相同的开头统计贡献,所以最后一个 *
\(_6\neq\)a
。
如果 *
\(_6\) 又 \(\neq\)b
,由于删 b
后补成的串一定以 a
或 b
结尾,所以不会重复。所以 *
\(_6=\)b
。
所以为了不让删 b
串的 a
和删 a
串的 b
匹配,必须把删 b
串变成 ******ab
。
ab******
\(=\)******ab
,所以这个 *
串长度确实得是偶数,且等于 abab...abab
。
所以求出每个相同段开头字符前连续 abab...ab
段的长度,然后减去重复即可。
代码
//George1123
#include <bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef vector<int> vi;
typedef pair<int,int> pii;
#define x first
#define y second
#define sz(a) (int)((a).size())
#define all(a) (a).begin(),(a).end()
#define R(i,n) for(int i=0;i<(n);++i)
#define L(i,n) for(int i=(n)-1;i>=0;--i)
constexpr int inf32=0x3f3f3f3f;
constexpr i64 inf64=0x3f3f3f3f3f3f3f3f;
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n,m;
string s;
cin>>n>>m>>s;
int now=0;
i64 res=1ll*n*(m-1);
R(i,n)if(i){
if(i>=2&&s[i]==s[i-2]) ++now;
else now=0;
if(s[i]!=s[i-1])
res+=1ll*n*(m-1)-now-1;
}
cout<<res<<'\n';
return 0;
}
/* stuff you should look for
* int overflow, array bounds
* special cases (n=1?)
* do smth instead of nothing and stay organized
* WRITE STUFF DOWN
* DON'T GET STUCK ON ONE APPROACH
*/
祝大家学习愉快!