子序列的个数
给定一个正整数序列,序列中元素的个数和元素值大小都不超过105, 求其所有子序列的个数。注意相同的只算一次:例如 {1,2,1}有子序列{1} {2} {1,2} {2,1}和{1,2,1}。最后结果对10^9 + 7取余数。
分析: 万能枚举,时间复杂度O(2^n)——因为每一项都可以选择取或者不取嘛。这个不可行,n太大了。
我们想想动态规划?严格来讲这个题不是一个dp的问题,因为它是一个数数(组合数学)的问题,不是一个优化的问题。我们来想想2^n个子序列是怎么来的——之前已经有2^(n-1)个了,再加上最后一个或者不加上最后一个,还有两种选择。
假设dp[i]表示前i项形成的子序列(含空)的个数。下标从1开始,初值是dp[0] = 1,对应代表空子序列。我们考虑第i项,如果所有的数都不相等,应该有dp[i] = dp[i – 1] * 2,其实就是考虑把第i个数放到最后或者不放到最后的情况。
然而,因为有可能有相同的数,我们假设第i个数出现之前最近的在j (j < i)位置也出现过,那么实际上我们这种简单*2会有重复,哪些子序列重复呢? 我们设数列为a, 并且下标从1开始。
于是我们有了递推式:
dp[i] = dp[i – 1] * 2 如果a[i]不在之前出现
dp[i] = dp[i – 1] * 2 – dp[j – 1],如果a[i]最近在j的位置出现过。
这里有一个问题:我们如何找到a[i]最近在哪里出现呢?我们可以用一个hash,更简单的方式是直接把a[i]放到数组下标里,记录它最后一次出现的位置,因为数据范围并不大,可以承受。 所以我们有了一个时间复杂度O(n)的动态规划解法。
注意最后答案是dp[n] – 1 因为我们需要减掉一个全空子序列。
伪代码:
输入: a[1..n]
辅助变量: have[x]表示目前x在a中最后一次出现的位置,如果没出现就是0,否则是一个正数。
另外,题目为了避免大数运算采用了取余数操作,请别忘记取余数操作。
最后,我们来提供输入输出数据,由你来写一段程序,实现这个算法,只有写出了正确的程序,才能继续后面的课程。
原来恰好以第j个数结尾的那些被我们算了两次,因为以第j个数结尾可以换成以第i个数结尾是一样的。如何计算出这个数的个数呢? 其实这个数等于dp[j – 1],因为前面(j – 1)个数的子序列最后跟上第j个数就可以了。
于是我们有了递推式:
dp[i] = dp[i – 1] * 2 如果a[i]不在之前出现
dp[i] = dp[i – 1] * 2 – dp[j – 1],如果a[i]最近在j的位置出现过。
这里有一个问题:我们如何找到a[i]最近在哪里出现呢?我们可以用一个hash,更简单的方式是直接把a[i]放到数组下标里,记录它最后一次出现的位置,因为数据范围并不大,可以承受。 所以我们有了一个时间复杂度O(n)的动态规划解法。
注意最后答案是dp[n] – 1 因为我们需要减掉一个全空子序列。
伪代码:
输入: a[1..n]
辅助变量: have[x]表示目前x在a中最后一次出现的位置,如果没出现就是0,否则是一个正数。
1
2
3
4
5
dp
[
0
]
=
1
for
i
=
1
to
n
do
dp
[
i
]
=
dp
[
i –
1
]
*
2
if
have
[
a
[
i
]]
>
0
then
dp
[
i
]
-=
dp
[
have
[
a
[
i
]] –
1
]
return
dp
[
n
] –
1
输入
第1行:一个数N,表示序列的长度(1 <= N <= 100000) 第2 - N + 1行:序列中的元素(1 <= a[i] <= 100000)
输出
输出a的不同子序列的数量Mod 10^9 + 7。
输入示例
4 1 2 3 2
输出示例
13
代码:
#include<iostream> #include<cstring> #define mod 1000000007 using namespace std; int a[100005]; int dp[100005]; //前i项的最大子序列个数 int s[100005]; int main() { int n; cin>>n; memset(s,0,sizeof(s)); for(int i=1;i<=n;i++) cin>>a[i]; dp[0]=1; //空子序列 for(int i=1;i<=n;i++) { dp[i]=(dp[i-1]*2)%mod; if(s[a[i]]>0) //a[i]和前面的一个数j重复,减去以j结尾的子序列个数 dp[i]=(dp[i]-dp[s[a[i]]-1]+mod)%mod; //前面(j – 1)个数的子序列最后跟上第j个数。 s[a[i]]=i; } cout<<(dp[n]-1)%mod<<endl; //减去空序列 return 0; }