FOJ 2129 子序列个数

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值