SDU程序设计思维Week10-作业
C-拿数问题II
Description
给 n 个数,每一步能拿走一个数,比如拿第 i 个数, Ai = x,得到相应的分数 x,但拿掉这个 Ai 后,x+1 和 x-1 (如果有 Aj = x+1 或 Aj = x-1 存在) 就会变得不可拿(但是有 Aj = x 的话可以继续拿这个 x)。求最大分数。
第一行包含一个整数 n (1 ≤ n ≤ 105),表示数字里的元素的个数
第二行包含n个整数a1, a2, ..., an (1 ≤ ai ≤ 105)
输出一个整数:你能得到最大分值。
Sample
Input
2
1 2
Output
2
Input
3
1 2 3
Output
4
Input
9
1 2 1 3 2 2 2 2 3
Output
10
Idea
dp[i]表示A[1…i]能拿到的最大分数
转移方程:dp[i]=max(dp[i-1],dp[i-2]+A[i])
题意:x被使用后,value等于x-1或x+1的元素不可以被用
- 把x转换成数组的下标,value转换成value=x出现的次数,记录这n个数value的左右边界left、right
- 初始化dp数组,dp[i]表示value∈[1,i]能拿到的最大分数
- 记录每个a的前溯元素pre,这个数组的意义是记录当前a的前一个出现次数不等于0的索引
- 根据需求改变之前的转移方程并扫描
对a[i]=0的元素忽略
对于前溯元素索引 != i-1时dp[i-2]+A[i]相当于dp[pre[i]] + a[i] * i
对于前溯元素索引 = i-1时dp[i-2]+A[i]相当于dp[pre[pre[i]]] + a[i] * i
Summary
这道题是动态规划的应用,转移方程是dp[i]=max(dp[i-1],dp[pre[i]]+a[i]*i / dp[pre[pre[i]]] + a[i] * i),把value转化成数组的下标,对应的值是出现次数。扫描时对于出现次数为0的value直接跳过
时间复杂度为O(n)
注意数据范围(1 ≤ ai ≤ 105)!!要使用long long存储dp!!
Codes
#include <iostream>
#include <algorithm>
using namespace std;
long long n, a[100200] = { 0 }, ans = 0, dp[100200] = {0}, pre[100200] = { 0 };
int main()
{
int left = 1e5 + 1, right = 0;
cin >> n;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
a[x]++;
if (left > x)left = x;
if (right < x)right = x;
}
dp[left-1] = 0;
dp[left] = a[left]*left;
pre[left] = 0;
int index = left;
for (int i = left + 1; i <= right; i++) {
if (a[i]) { pre[i] = index; index = i; }
}
for (int i = left+1; i <= right; i++) {
if (a[i] == 0)continue;
if (pre[i] != i - 1) {
dp[i] = max(dp[i - 1], dp[pre[i]] + a[i] * i);
}
else {
dp[i] = max(dp[i - 1], dp[pre[pre[i]]] + a[i] * i);
}
}
for (int i = left; i <= right; i++)
{
ans = ans > dp[i] ? ans : dp[i];
}
cout << ans << endl;
}