Wannafly挑战赛21: C. 大水题(DP)

题目描述

现在给你N个正整数ai,每个数给出一“好数程度” gi(数值相同但位置不同的数之间可能有不同的好数程度)。对于在 i 位置的数,如果有一在j位置的数满足 j < i 且 ai=aj,则你可以将位于[i,j]闭区间内的序列评为“好序列”,然后获得∑gk(j≤k≤i)(此闭区间内“好数程度”之和)分数。

注意: 在所有情况下,每个数都只能被一个”好序列”包含(只能与其他相应数被评为”好序列”一次);在符合要求的情况下,”好序列”的评定次数不受限制,且通过不同”好序列”获得的分数可以累加。
 

输入描述:

第一行有一个正整数N。
接下来的一行有N个正整数ai,表示意义如上。
(保证ai在32位整型范围内)
接下来的一行有N个正整数gi,表示ai的”好数程度”。
(保证gi在64位整型范围内)

输出描述:

一个整数,你可以获得的最大分数(通过不同”好序列”获得的分数可以累加),保证答案在64位整型范围内。

示例1

输入

7
1 2 1 2 3 2 3
1 4 3 4 3 4 5

输出

23

很容易想到一个n²的DP:dp[i]表示前i个数字能获得的最大分数,有dp[i] = max(dp[j-1]+sum(j, i), (a[i]==a[j]))

但是这道题n=300000肯定不行,一般这种情况就是看能不能线段树优化,或者看有没有决策单调性

容易证明:这个DP具有决策单调性,我们假设对于dp[x],它在y点取得最大值,也就是dp[x] = dp[y-1]+sum(y, x),那么除了y点以外其它所有点都会毫无意义,永远不可能最优,所以每次转移只需要保留最优的一个点即可

也就是dp[i] = max(dp[pre[i]-1]+sum(~), dp[best[pre[i]]-1]+sum(~))

其中pre[i]表示上一个和a[i]相等的数的位置,best[i]表示dp[i]的最优决策在哪一点

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<map>
#include<string>
#include<math.h>
#include<queue>
#include<stack>
#include<iostream>
using namespace std;
#define LL long long
#define mod 1000000007
map<int, int> p;
int a[300005], last[300005], bet[300005];
LL dp[300005], sum[300005];
int main(void)
{
	LL ans;
	int n, i;
	scanf("%d", &n);
	for(i=1;i<=n;i++)
	{
		scanf("%lld", &a[i]);
		if(p.count(a[i])==0)
			p[a[i]] = i;
		else
		{
			last[i] = p[a[i]];
			p[a[i]] = i;
		}
	}
	for(i=1;i<=n;i++)
	{
		scanf("%lld", &sum[i]);
		sum[i] += sum[i-1];
	}
	ans = 0;
	for(i=1;i<=n;i++)
	{
		if(last[i]!=0)
		{
			dp[i] = dp[last[i]-1]+sum[i]-sum[last[i]-1];
			bet[i] = last[i]-1;
			if(bet[last[i]]!=0 && dp[bet[last[i]]]+sum[i]-sum[bet[last[i]]]>dp[i])
			{
				dp[i] = dp[bet[last[i]]]+sum[i]-sum[bet[last[i]]];
				bet[i] = bet[last[i]];
			}
		}
		ans = max(ans, dp[i]);
	}
	printf("%lld\n", ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/81395068