牛客34442J - 收集者
题意
- 给出一个长度为 n 的 01串,你需要从中选出一个开头为 1 的子序列
- 能选出多少种长得不一样的子序列。
- (题目额外要求:如果给出的01串中有0,最后的答案额外加1。)
思路
错误思路:利用组合数公式
正确思路:DP
- dp[i] 代表截止到第 i 位(第 i 位可选可不选),有多少种子序列。
- 对于每一位,有选和不选两种选择。
- 如何去重?
- 以下面的01串举例:
- 100011
假设第 cur 位是 0,在它之前上一位是 0 的位置是 pre,
- 那么第pre−1的dp结果,经由pre和cur会出现这种情况:
- 第pre位给某个子序列追加了0,而第cur位没有给它追加0
- 第pre位没有给某个子序列追加了0,而第cur位给它追加了0
- 某一个子序列经由这两种情况,最后的长相是一样的,应该去掉其中一个。
- 总共应该去掉几个呢?
- 去掉 dp[pre−1] 个。
假设第 cur 位是 1,在它之前上一位是 1 的位置是 pre,
- 跟上面的一样。会出现第pre位给某个子序列追加了1,而第cur位没有给它追加1;和第pre位没有给某个子序列追加了 1,而第cur位给它追加了 1 的情况。
- 注意看 i=6 的情况,重复的元素的元素数量,就等于 dp[pre−1],因为“1,10,100,1000”在经由 i=5 和 i=6 时,出现了只有一次加上了 1 的情况。
大概是全网最详细的解析了叭,确定不点个赞再走嘛?
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#define int long long
const int N = 1e6+10;
const int MOD = 1e9+7;
using namespace std;
char str[N];
int dp[N],pre[2];
bool have0=false;
int n;
void Solve()
{
int st=0;
while (str[st]!='1')
st++;
dp[st]=1;
for (int i=st+1; i<=n; i++)
{
dp[i] = dp[i-1] * 2 % MOD - (pre[str[i]-'0']>0)*(dp[ pre[ str[i]-'0' ]-1 ]);
dp[i] += MOD, dp[i]%=MOD;
pre[ str[i]-'0' ] = i;
}
printf("%lld\n",(dp[n]+have0)%MOD);
}
signed main()
{
scanf("%s",str+1);
n=strlen(str+1);
for (int i=1; i<=n; i++)
{
have0 |= (str[i]=='0');
}
Solve();
return 0;
}