hdu-6698 Coins 优先队列 dp

hdu-6698 Coins 优先队列 dp

题意

给n个数对,每个对可以取两次价值,第一次取得左边的ai,第二次才可以取得右边的bi。

现在要求解2n个问题,问题i表示输出,取i次价值能获得的最大价值。

思路

我们集训队的yyj巨巨提出了非常棒的猜想。思考的方向应该是,用优先队列维护剩余的对中,还能取得的最大价值,可能第一次取左边的,也可能是第二次取右边的,由于每对数都是最多有序取两次,每次从前一个状态转移会有比较多的讨论,比如某个位置的元素有没有之前被取过,取过几次。(参考标解)(好吧我不懂)

那么假如我们把两步转移连起来看,可以归纳成两种转移,即取了两个不同位置的元素a或b各一次,或者连续两次取了相同位置的ab元素。

可以大概想一下有这样的转移:
$$
dp[i]=dp[i-2]+max(node[i].v+node[j].v, node[k].a+node[k].b)
$$

  • 注意上式中ijk都代表剩下可行的位置中的最优解,也就是用优先队列贪心的。
  • 注意v代表a或者b,因为,我们之前可能单独对这个位置取过单次的,所以这里需要做一点判断。(但其实很简单)
  • 注意在这样的转移下,奇数的答案和偶数的答案可以独立转移。dp[1]显然是最大的那个a。

上面的想法略懂即可,如果没有直接能自己敲出来也不要气,这个题确实比较难敲。

下面讲一下我是如何实现的,感谢我的队友Lücy帮我一起Debug。

核心在于,两个优先队列维护(或者说存储),剩余可取的位置最大的价值与位置,这里我用了pair。

我们有两种方案,一种是两个不同位置各取一次,一种是同一个位置取两次。

第一个优先队列a就是为不同位置的方案服务的。但是为了防止和另一种方案产生交集,我初始化在优先队列里塞所有的a,当我使用掉某个a的时候,就把b升级成a再还回到优先队列里。这样就保证队列中所有方案都是在不同位置上的。

第二个优先队列ab与上面类似,不过存的是(ai+bi,i)

used数组记录的是位置i被使用的次数,因为我们两个队列很难一次性保持统一地删元素,那么可以在取元素地时候判掉。

再退回到主函数,应该容易发现奇数偶数的答案可以分别计算,只是起点不同而已,注意计算完ans[1]之后,也要模拟一次存取元素的过程。我的操作是,干脆一开始就把对应位置的,used更新了,也把初始的a变成b,这样可以复用同样的代码。

可能中间有一些奇怪的操作,见谅。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <stdio.h>
#include <algorithm>
#include <queue>

#define _debug(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;
typedef long long ll;
typedef pair<int, int> node;
int n, q;
int kase;
node a[MAXN];
node ab[MAXN];
short used[MAXN];
int ans[MAXN << 1];

void solv(int i) {
//数组转优先队列。
priority_queue<node> qa(a, a + n);
priority_queue<node> qab(ab, ab + n);

for (int x, xi, y, yi, z, zi; i <= 2 * n; i += 2) {
ans[i] = ans[i - 2];
z = x = y = -1;
zi = xi = yi = -1;

while (!qa.empty() && used[qa.top().second] >= 2)qa.pop();
if (!qa.empty() && qa.size() >= 2) {
x = qa.top().first;
xi = qa.top().second;
qa.pop();

while (!qa.empty() && used[qa.top().second] >= 2)qa.pop();
if (!qa.empty()) {
y = qa.top().first;
yi = qa.top().second;
qa.pop();
} else {
qa.emplace(x, xi);
x = xi = -1;
}
}

while (!qab.empty() && used[qab.top().second] > 0)qab.pop();
if (!qab.empty()) {
z = qab.top().first;
zi = qab.top().second;
qab.pop();
}

if (x + y > z) {
ans[i] += x + y;
used[xi]++;
used[yi]++;

if (used[xi] == 1)qa.emplace(ab[xi].first - x, xi);
if (used[yi] == 1)qa.emplace(ab[yi].first - y, yi);
if (z != -1)qab.emplace(z, zi);
} else {
ans[i] += z;
used[zi] += 2;
if (x != -1)qa.emplace(x, xi);
if (y != -1)qa.emplace(y, yi);
// if (z != -1)qa.emplace(z - a[zi].first, zi);
}
}
}

int main() {
scanf("%d", &kase);
while (kase--) {
scanf("%d", &n);
int maxx = 0, maxi = 0;
for (int x, y, i = 0; i < n; i++) {
scanf("%d%d", &x, &y);

if (x > maxx) {
maxx = x;
maxi = i;
}

a[i] = {x, i};
ab[i] = {x + y, i};
}

for (int i = 0; i < n; ++i)used[i] = 0;
solv(2);

for (int i = 0; i < n; ++i)used[i] = 0;
ans[1] = maxx;
a[maxi].first = ab[maxi].first - a[maxi].first;
used[maxi] = 1;
solv(3);

for (int i = 1; i <= 2 * n; ++i)
printf("%d%c", ans[i], " \n"[i == n + n]);

}
return 0;
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×