来源: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。