Atcoder ABC214 F - Substrings
Description
给你一个字符串 \(S\),从中选出一个字符串 \(T\),要求相邻两个位置不能同时选,不能改变顺序,求能选出多少种不同的 \(T\)。对 \(10^9 + 7\) 取模。
\(n \le 2 \times 10^5\)
Solution1
考虑 DP。
设 \(f_{i,0/1}\) 表示考虑到第 \(i\) 位,第 \(i\) 位选不选的方案数。
如果不考虑重复的情况,显然有:
\[f_{i,0} = f_{i-1,0} + f_{i-1,1}
\]
\[f_{i,1} = f_{i-1,0}
\]
这个和树上点独立集一样的。
考虑如何去重。
设第 \(i\) 位的字母为 \(c\),稍微改一下转移方程:
\[f_{i,1} = f_{i-1,0} - \sum_{j=1}^{i-1} f_{j,1} [s_j = c]
\]
为什么把前 \(i-1\) 位中结尾为 \(c\) 的方案去掉?
仔细考虑一下,减去的这些方案数也和当前位置 \(c\) 一样,都是在最后面放一个 \(c\) 为了避免重复就需要去掉这一部分的方案。
考虑用二十六棵树状数组去维护前面 \(i-1\) 个位置以某个字母结尾的方案数和。转移方程变为:
\[f_{i,1} = f_{i-1,0} - S[c].Query(i-1)
\]
就可以在 \(\mathcal O(n \log n)\) 的时间内完成转移。
Code1
只放关键部分代码:
int n;
char s[MAXN];
int f[MAXN][2];
struct Bit {
int sum[MAXN];
int lb(int x) { return x & -x; }
void Modify(int x, int k) { for(; x <= n; x += lb(x)) sum[x] = (sum[x] + k) % mod; }
int Query(int x) { int res = 0; for(; x; x -= lb(x)) res = (res + sum[x]) % mod; return res; }
}S[26];
signed main()
{
cin >> s + 1;
n = strlen(s + 1);
f[1][1] = 1, S[s[1] - 'a'].Modify(1, f[1][1]);
for(int i = 2; i <= n; ++i) {
f[i][0] = (f[i - 1][0] + f[i - 1][1]) % mod;
f[i][1] = (f[i - 1][0] + 1 - S[s[i] - 'a'].Query(i - 1) + mod) % mod;
S[s[i] - 'a'].Modify(i, f[i][1]);
}
printf("%lld\n", (f[n][0] + f[n][1]) % mod);
return 0;
}
Solution2
下面介绍题解的做法。
设 \(f_i\) 表示以 \(s_i\) 为结尾的方案数。
设从后向前看第一个和 \(s_i\) 相同的字符的位置为 \(k\)。如果没有的话 \(k=0\)
转移方程:
\[f_i = \sum_{j=k}^{i-1} f_j
\]
去重的道理和上面的差不多吧:如果从 \(<k\) 的位置转移过来,那么得到的字符串方案其实已经在 \(f_k\) 处统计过了。
转移的时候直接暴力转移。
看上去复杂度很高,但它是均摊是 \(\mathcal O(n)\) 的。
为了使每个位置都尽可能向前转移,一个的最劣的字符串是:
\(abcdef...efdcba\)
所以最坏复杂度也只有 \(\mathcal O(26n)\)。
Code2
扔一个 std
#include<bits/stdc++.h>
using namespace std;
int main(){
string S; cin >> S;
int N = S.size();
vector<long long> dp(N+2); dp[0] = 1;
for(int i = 0; i < N; i++){
for(int j = i-1; ; j--){
dp[i+2] = (dp[i+2] + dp[j+1]) % 1000000007;
if(j == -1 || S[j] == S[i]) break;
}
}
long long ans=0;
for(int i = 2; i < N+2; i++) ans += dp[i];
cout << ans%1000000007 << endl;
}