传送门:51nod 1202
思路:
设 dp[ i ] 表示前 i 个字符可以形成的非重复子序列个数。
当第 i 个字符在之前未出现过时,有 dp[ i ] = dp[ i-1 ] * 2 +1 ;即前 i-1 个字符可以形成的子序列数 + 将第 i 个字符加到前 i-1 个字符形成的子序列后面可以形成的子序列数 + 第 i 个字符本身。
当第 i 个字符在之前出现过时,有 dp[ i ] = dp[ i-1] * 2 - dp[ j-1] ,其中 j 为第 i 个字符最近出现的位置,即前 i-1 个字符可以形成的子序列数 + 将第 i 个字符加到前 i-1 个字符形成的子序列后面可以形成的子序列数,这样一来肯定有重复的,再减去重复的子序列数。
为什么减去的是 dp[ j-1] 而不是 dp[ j ] 呢?假设字符 X 重复出现的位置如上图所示,在两个 X 字符的前面各有一段字符串 A 和 B。哪些是重复的呢?只有 A 段和第一个 X 与 A 段与第二个 X 形成的子序列会重复,其他的都不重复。所以减去的是 dp[ j ] 喽?不是,dp[ j ] 表示的是以第 j 个字符结尾的有多少个子序列,这其中包括了 A 段自己可以形成的子序列数,然而这些并没有重复,重复的只是第二个 X 替换第一个 X 与 A 形成的子序列数,所以减去的是 dp[ j-1 ] .
注意:
1.在寻找第 i 个字符最近出现的位置的时候,如果遍历搜索会超时,所以我们可以用 vis 数组记录每个字符上次出现的位置,这样同时也记录了该字符是否出现过。
2.为什么取模的时候不能直接对 dp[ i ] 取模呢?因为在相加的时候,dp[ i ] = dp[ i-1 ] * 2 +1 中的 dp[ i-1 ] 并不是原本的数值,而是对 1e9+7 取模后的数值,先相加后取模会出错。需要利用同余模的加法定理: ( a + b ) % m =( a % m + b %m ) % m.
PS:
最后讲一下自己的一点体会,动态规划有点利用分类加法和分步乘法的原理,看看每个状态有哪些情况,分类讨论。然后就是在假设完 dp 数组代表的含义后我们就假设 dp[ i-1 ] 已经知道了,再求 dp[ i ] ,不然自己会被各种复杂的情况给搞懵。
代码:
#include<stdio.h>
#include<string.h>
int main()
{
int i,n,mod=1e9+7;
int a[100010],vis[100010],dp[100010];
while(~scanf("%d",&n))
{
for(i=1;i<=n;i++) scanf("%d",&a[i]);
dp[1]=1;
memset(vis,0,sizeof(vis));
vis[a[1]]=1;
for(i=2;i<=n;i++)
{
if(!vis[a[i]]) dp[i]=((dp[i-1]<<1)%mod+1)%mod;
else dp[i]=((dp[i-1]<<1)%mod-dp[vis[a[i]]-1]+mod)%mod;
vis[a[i]]=i;
}
printf("%d\n",dp[n]);
}
return 0;
}