HDU 4455 Substrings (2012年杭州赛区现场赛C题)

1.题目描述:点击打开链接

2.解题思路:本题利用dp解决。不过这个dp的思路的确比太容易想到。需要观察规律才能发现。我们可以从贡献值的角度考虑。以题目中给的样例来说明这种方法。

通过观察相邻两个w值,我们会发现一个事实:每个大区间都包含了小区间的解(这里的解即原题中的sum值)。但是这还不够,观察图上标记为红色的数字,它们可以看做是上一个w值对应的区间多出来的新数字,如果我们可以算出这些红色数字的贡献值,那么我们就能得到当前w下的解。那么该如何计算呢?


继续观察后会发现,每次w增加1,区间个数就会减少1个,也就是说,w-1时候的最后一个区间的结果要被减掉,才是w-1的解对当前w的贡献。接下来看红色数字的贡献,由于当前区间长度为w,那么某个数字i和前一个i的距离差<w时,这些数字都不能算入。比如以w=5的情况为例,w=5时候,第一个红色数字4要被算入,因为它和前一个4(如果不存在前一个数就看做在0位置,数组的起始下标从1开始)距离为5,不满足距离<w,因此要被算入,而第二个红色数字4和前一个4距离为1,满足1<w的要求,因此不能被算入。也就是说,我们需要知道数字的距离分别有几个,比如距离为x的有dis[x]个,那么w-1时候的红色数字的贡献值假设为sum,那么w时候,红色数字的贡献就是sum-dis[w-1]。同时,我们用last[i]表示最后i个数字有几种不同的数字。于是得到如下的递推式:

dp(w)=dp(w-1)-last(w-1)+(sum-dis[w-1]);

上式中的sum是w-1时候红色数字对答案的贡献。当w=0时候,sum=n。这样就可以用O(1)时间得到所有的dp(w)了。当然,根据上述分析,我们需要提前预处理出来dis[i]和last[i],详细过程见代码。

本题最终的时间复杂度是O(N+Q)。

3.代码:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>

using namespace std;

#define me(s) memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
#define pb push_back
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;


const int N = 1000010;
int a[N], dis[N], vis[N], last[N], sum;
ll dp[N];

int n;

int main()
{

	while (~scanf("%d", &n) && n)
	{
		sum = n; //sum是前一个w值中红色数字对答案的贡献值
		me(vis); me(dis);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			dis[i - vis[a[i]]]++; //统计不同距离的个数
			vis[a[i]] = i;//更新a[i]上一次出现的位置
		}
		me(vis);
		last[1] = 1;//last[i]表示最后i个数中有几个不同的数
		vis[a[n]] = 1;//标记a[n]访问过
		for (int i = 2; i <= n; i++)
			if (!vis[a[n - i + 1]])//发现新的没有出现过的数,last[i]++
			{
				vis[a[n - i + 1]]++;
				last[i] = last[i - 1] + 1;
			}
			else last[i] = last[i - 1];
			dp[1] = n;
		for (int i = 2; i <= n; i++)
		{
			sum -= dis[i - 1];//更新sum,得到当前w时候红色数字对答案的贡献值
			dp[i] = dp[i - 1] - last[i - 1] + sum; 
		}
		int q;
		scanf("%d", &q);
		for (int i = 0; i<q; i++)
		{
			int w;
			scanf("%d", &w);
			printf("%I64d\n", dp[w]);
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值