FZOJ.Problem 2129 子序列个数
思路: d p dp dp,令前 i i i个数的子序列个数为 d p [ i ] dp[i] dp[i],
若 i i i与前面 i − 1 i-1 i−1个数都不同则 d p [ i ] = 2 × d p [ i − 1 ] + 1 dp[i]=2\times dp[i-1]+1 dp[i]=2×dp[i−1]+1.
因为前面 i − 1 i-1 i−1个子序列都可以加上 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 i−1个字符串不同
即 d p [ i ] = 2 × d p [ i − 1 ] dp[i]=2\times dp[i-1] dp[i]=2×dp[i−1],然后减去重复的子序列个数。
显然我们需要找到距离 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[i−1]−dp[id]
几个 W A WA WA点:要开 L L LL LL,注意取模保持答案是非负数。
时间复杂度: O ( n ) O(n) O(n)
因为此题数据较大,所以用快读比较好,亲测从 1 s → 0.3 s 1s\rightarrow0.3s 1s→0.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;
}