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;
}
posted @ 2025-03-09 15:29  KS_Fszha  阅读(33)  评论(0)    收藏  举报