子序列个数


子序列个数
Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u

Description

子序列的定义:对于一个序列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的子序列。

对于给出序列a,请输出不同的子序列的个数。

(由于答案比较大,请将答案mod 1000000007)

Input

输入包含多组数据。每组数据第一行为一个整数n(1<=n<=1,000,000),

表示序列元素的个数。

第二行包含n个整数a[i] (0<=a[i]<=1,000,000)表示序列中每个元素。

Output

输出一个整数占一行,为所求的不同子序列的个数。由于答案比较大,
请将答案mod 1000000007。

Sample Input

4
1 2 3 2

Sample Output

13

Hint

其中40%数据点1<=n<=1000。

算法分析:

设 f(k)为k长度的序列的子序列个数,那么很显然有以下推论:

  • f(k)的个数包括f(k-1)的个数,因为f(k-1)的每一个都是f(k)的子序列,然后把f(k-1)的每个序列和a[k]组合起来,这些序列也是f(k)的子序列,个数还是f(k-1),载加上单独的a[k],那么

f(k)=2*f(k-1)+1

  • 上面这个表达式是当a[k]和前面的数都不同的时候的情况,如果a[k]在前面出现过的话,那f(k)的个数除了上面那些的话:

    • 还需要减去最近一次出现a[k]这个数值的地方-1的子序列个数,因为这些算重复了
    • +1也没有了,因为f(a[k]上次出现的位置)包括了a[k]单独算一次的情况

f(k)=2*f(k-1)-f(a[k]上次出现的位置-1)

有了这两个表达式,就是一个完整的递推关系了,a[k]上次出现的位置的保存,可以用一个hash表来存储,这样速度很快,但是题目说a[k]的范围是0到220,那可以用一个220的数组来存储,反正也不会溢出,省得用hash了。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

const int maxn = 1e6 + 5;
const int mod = 1000000007;
long long f[maxn];        //避免超int
int A[maxn], ls[maxn];
int main()
{
    int n;
    while(~scanf("%d", &n)) {
      
        memset(ls, 0, sizeof(ls));
        for(int i = 1; i <= n; i++) {
        	scanf("%d", &A[i]);
        }

        for(int i = 1; i <= n; i++) {
            if(ls[A[i]] == 0) f[i] = (2 * f[i - 1] + 1) % mod;
            else f[i] = (2 * f[i - 1] - f[ls[A[i]] - 1] + mod) % mod;
            ls[A[i]] = i;
        }
        printf("%lld\n", f[n]);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值