来源:http://acm.fzu.edu.cn/problem.php?pid=2129
概述:给一个整数序列,问一共可以生成多少种不同的子序列。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
理论分析
用递推的思维求解。设序列的前i个元素可以生成d[i]种不同的子序列,第i个元素为a[i]。
(1)如果a[i]在a[1]~a[i-1]中没有出现过,那么d[i]可以分成三类构成方式
①d[i-1]
②d[i-1]和a[i]
③a[i]
共有2d[i-1]+1种不同的子序列,即d[i] = 2d[i-1] + 1。
(2)如果a[i]在a[1]~a[i-1]中出现过,记最近一次出现的下标所在位置为p。那么,类似于情况(1)的构造方式,①②代数累加后,要去除d[p-1]的重复计算——d[p-1]和a[p]组合,与d[p-1]和a[i]组合是相同的,重复计算了一次,要扣除。另外,也不存在③的构造方法,因为d[i-1]中,已经有a[p],等同与a[i]。
故此时公式为d[i] = 2d[i-1] - d[p-1]。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
简化算法
重新定义数组a[t]的含义:如果t尚未出现,则a[t]记为-1;如果t已出现过,且最近一次出现在位置p,则a[t]记为d[p-1]的值。
那么,可以统一理论分析中的(1)(2)公式——若当前为第i个序列值,值为t,则有d[i] = 2d[i-1] - a[t]。
从公式中,也可以发现不必要为d开辟数组,只需用一个值s,动态更新:s = 2s - a[t]。
最后,要注意代码中的第18行是必须的,因为计算过程s可能出现负值,而C语言的%运算符不是“真正的取余”运算——它会忽略负号,算出余数后,再增加一个负号。真正的取余运算应该是将s加上MOD的一个倍数,使得s的值在0~MOD-1之间。
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 1000001, MOD = 1000000007;
int main()
{
int n, a[MAXN];
while (scanf("%d", &n) != EOF)
{
memset(a, -1, sizeof(int)*MAXN);
int s = 0, t, s0;
for (int i = 0; i < n; i++)
{
scanf("%d", &t);
s0 = s;
s = (2*s - a[t]) % MOD;
if (s < 0) s += MOD;
a[t] = s0;
}
printf("%d\n", s);
}
return 0;
}
花絮:这段代码只能用VC++提交,我不知道G++为什么会RE。