子序列的个数(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。
第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 }