子序列的个数(DP计数)

这个问题让我知道了动态规划除了能用来求最优解,还可以用来做计数 = =

然后,取模的时候如果有减法是这个样子取模的: (a-b)%MOD = ((a-b)%MOD+MOD)%MOD;

因为(a-b)可能会产生负数。

 

问题概述:

给定一个正整数序列,序列中元素的个数和元素值大小都不超过105, 求其所有子序列的个数。

注意相同的只算一次:例如 {1,2,1}有子序列{1} {2} {1,2} {2,1}和{1,2,1}。最后结果对10^9 + 7取余数。

子序列的定义:对于一个序列a=a[1],a[2],......a[n]。则非空序列a'=a[p1],a[p2]......a[pm]为a的一个子序列,其中1<=p1<p2<.....<pm<=n。

例如4,14,2,3和14,1,2,3都为4,13,14,1,2,3的子序列。
 
输入

第1行:一个数N,表示序列的长度(1 <= N <= 100000) 第2 - N + 1行:序列中的元素(1 <= a[i] <= 100000)

输出

输出a的不同子序列的数量Mod 10^9 + 7。

 

解决:

首先,这个题并不能采用暴力枚举的方式的来解决。对于一个非空集合,它的子集个数是2^n(含空集),n的范围太大了。

我们来考虑动态规划,假设 dp[i] 表示到第 i 个元素时已形成的不重复的子序列个数(含空),数组下标从1开始;初始dp[0] = 1, 对应空集。

考虑第 i 项:

   如果之前一直没有重复的元素,那么 dp[i] 的值就是 dp[i-1] * 2 ,因为前面已经形成了 dp[i-1] 个值,后来加入的新元素 a[i] 可以和前面的

dp[i-1]个 序列都组成一个新序列, 所以dp[i] = dp[i-1] * 2 。

  那如果有重复的呢,我们假设这个数上一次在数组的 j 位置出现过,那么我们这种 dp[i] = dp[i-1] * 2 的计算就会有重复, 那是那些地方有重复呢,

对啦,就是位置 j 那里了,位置 j 之前的数又被我们重复计算了一次。而我们多算的次数其实就是 dp[j-1] 。于是我们有了递推式:

dp[i] = dp[i – 1] * 2   (如果a[i]之前没有出现)
dp[i] = dp[i – 1] * 2 – dp[j – 1]   (如果a[i]最近在j的位置出现过)

怎么保存a[i] 最近一次出现的位置就留给大家思考啦~

 

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #define MOD 1000000007
 5 #define maxn 100005
 6 #define ll __int64 
 7 using namespace std;
 8 ll dp[maxn];
 9 int a[maxn],have[maxn];
10 int main()
11 {
12     int n;
13     memset(have,0,sizeof(have)); // 用来保存 a[i] 最近一次出现位置的数组 
14 //    memset(dp,0,sizeof(dp));
15     dp[0]=1;         //初始化 
16     scanf("%d",&n);    
17     for(int i=1;i<=n;i++)
18     {
19         scanf("%d",&a[i]);  
20         if(have[a[i]] > 0) dp[i] = ( ( (dp[i-1] * 2) - (dp[have[a[i]]- 1]) ) % MOD + MOD) % MOD;    // 减法的取余 
21         else dp[i] = (dp[i-1] * 2) % MOD;    
22         have[a[i]] = i;    
23     }    
24     printf("%I64d\n",dp[n]-1);
25     return 0;    
26 }

 

posted @ 2015-08-10 16:15  Ember  阅读(2843)  评论(2编辑  收藏  举报