FZOJ.Problem 2129 子序列个数

传送门

思路: d p dp dp,令前 i i i个数的子序列个数为 d p [ i ] dp[i] dp[i]

i i i与前面 i − 1 i-1 i1个数都不同则 d p [ i ] = 2 × d p [ i − 1 ] + 1 dp[i]=2\times dp[i-1]+1 dp[i]=2×dp[i1]+1.

因为前面 i − 1 i-1 i1个子序列都可以加上 a [ i ] a[i] a[i]组成新的子序列,然后再加上 a [ i ] a[i] a[i]本身也是一个子序列。

i i i在前面出现过了,就先假设 a [ i ] a[i] a[i]与前面 i − 1 i-1 i1个字符串不同

d p [ i ] = 2 × d p [ i − 1 ] dp[i]=2\times dp[i-1] dp[i]=2×dp[i1],然后减去重复的子序列个数。

显然我们需要找到距离 i i i最近的与 a [ i ] a[i] a[i]相同的数的前面一个位置。

i d = p o s a [ i ] − 1 , d p [ i d ] id=pos_{a[i]}-1,dp[id] id=posa[i]1,dp[id]的个数就是重复的子序列个数。

因为能与 p o s pos pos构成子序列同样也能与 a [ i ] a[i] a[i]构成完全一样的子序列。

这里为什么不加 1 1 1呢,显然因为之前 d p [ p o s ] dp[pos] dp[pos]已经加过了,所以不然再重复加。

d p [ i ] = 2 × d p [ i − 1 ] − d p [ i d ] dp[i]=2\times dp[i-1]-dp[id] dp[i]=2×dp[i1]dp[id]

几个 W A WA WA点:要开 L L LL LL,注意取模保持答案是非负数。

时间复杂度: O ( n ) O(n) O(n)

因为此题数据较大,所以用快读比较好,亲测从 1 s → 0.3 s 1s\rightarrow0.3s 1s0.3s

思考:显然此种方法可以用于字符串中求本质不同的子序列个数。另外此题一个孪生题:求本质不同的子串,但是方法好像不一样, m a r k mark mark一下 。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e6+5,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
int a[N],vis[N];
ll dp[N];
inline void read(int &x){ 
	x=0;int w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
	for(;ch>='0'&&ch<='9';ch=getchar())
		x=(x<<3)+(x<<1)+(ch&15);
	x*=w; 
}
int main(){
	int n; 
	while(~scanf("%d",&n)){
	for(reg int i=1;i<=n;i++){
		read(a[i]),dp[i]=vis[a[i]]=0;
	}
	for(reg int i=1;i<=n;i++){
		if(!vis[a[i]]) dp[i]=(2*dp[i-1]+1)%mod;
		else  dp[i]=(2*dp[i-1]-dp[vis[a[i]]-1]+mod)%mod;
		vis[a[i]]=i;
	} 
	printf("%lld\n",dp[n]);
	}
	return 0;
}