Vjudge DMOJ-dp1p4 Counting Subsequences 题解 [ 绿 ] [ 前缀和优化 dp ] [ 本质不同子序列计数 ]
Counting Subsequences:本质不同子序列计数模板,dp 状态的设计比较值得借鉴。
我竟然被这种傻逼板子卡了一会,拜谢 sdy 讲的这个板子。
思路
这题运用了一个常见的状态设计 trick:将状态设计为以某个位置为结尾的增量,即某个位置为结尾的贡献。
于是我们定义 \(dp_i\) 表示以 \(i\) 结尾的、前面未出现过的(这次新增的)本质不同子序列个数。
转移是:
\[dp_i=\sum_{j=pre_i}^{i-1} dp_j
\]
其中 \(pre_i\) 表示上一个与 \(a_i\) 相同的字符的位置,开桶记录即可。
这个状态转移的含义就是在 \(pre_i \sim i-1\) 中的新增子序列后面在加这个字符一定能形成新的本质不同子序列,而在 \(pre_i\) 之前的话就相当于被 \(dp_{pre_i}\) 包含了,那么个数就算重了,因此是不行的。
注意特判单个字符的情况,也就是 \(pre_i\) 不存在时,\(dp_i\) 要额外加 \(1\)。
时间复杂度 \(O(n)\)。
代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=100005;
const ll mod=10007;
int n,pre[30];
char s[N];
ll dp[N];
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>s+1;
n=strlen(s+1);
for(int i=1;i<=n;i++)
{
dp[i]=(dp[i-1]-dp[max(0,pre[s[i]-'a']-1)]+(pre[s[i]-'a']==0)+mod)%mod;
pre[s[i]-'a']=i;
dp[i]=(dp[i]+dp[i-1])%mod;
}
cout<<dp[n];
return 0;
}

浙公网安备 33010602011771号