引爆炸弹
【问题描述】
有n个炸弹,有些炸弹牵了一根单向引线(也就是说引线只有在这一端能被炸弹点燃),只要引爆了这个炸弹,用引线连接的下一个炸弹也会爆炸。每个炸弹还有个得分,当这个炸弹被引爆后就能得到相应得分。
现在要你引爆k个炸弹,使得分最大。
【输入】
第1行两个整数n、k。
接下来n行每行两个整数a[i]、b[i]。a[i]表示这个炸弹用引线连接的下一个炸弹,如果a[i]为0,则表示这个炸弹没连接引线。b[i]表示这个炸弹的得分。
【输出】
仅一个数,表示最大得分。
【样例输入输出】
bomb.in | bomb.out |
8 2 0 1 1 1 2 100 2 100 0 1 5 1 6 2 6 2 | 202 |
【数据范围】
1≤b[i]≤1000000
对于30%的数据,n≤1000,k≤30;
对于60%的数据,n≤50000,k≤100;
对于100%的数据,n≤200000,k≤500;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long int ll;
ll f[200001];
ll g[200001];
ll a[200001], b[200001];
ll s[200001];
ll d[200001];//节点的入度
ll v[200001];
ll ans = 0;
ll t, n, k;
void find_it(ll l, ll r)
{
ll i, j, t, mid;
i = l;
j = r;
mid = f[s[(i + j) / 2]];
while (i <= j) {
while (f[s[i]] > mid) { ++i; }
while (f[s[j]] < mid) { --j; }
if (i <= j) {
t = s[i];
s[i] = s[j];
s[j] = t;
++i;
--j;
}
}
if (l < j) find_it(l, j);
if (i < r) find_it(i, r);
}
void process()
{
ll i, j;
while (t != 0) {
i = t;
t = d[i];//下一个度为0的节点
f[i] = b[i] + f[i];
if (a[i] != 0) {
if (f[a[i]] < f[i]) {
f[a[i]] = f[i];
g[a[i]] = i;//记录父亲节点
}
d[a[i]]--;
if (d[a[i]] == 0) {
d[a[i]] = t;
t = a[i];
}
}
}
for (i = 1; i <= n; ++i) s[i] = i;
find_it(1, n);
ans = 0;
for (i = 1; i <= n; ++i) {
if (v[s[i]] == 0) {
ans += f[s[i]];
--k;
if (k == 0) break;
j = s[i];
while (j != 0) {
v[j] = 1;
j = g[j];
}
}
}
}
int main()
{
FILE *in, *out;
in = fopen("bomb.in", "rt");
out = fopen("bomb.out", "wt");
ll i;
fscanf(in, "%lld%lld", &n, &k);
for (i = 1; i <= n; ++i) {
fscanf(in, "%lld%lld", &a[i], &b[i]);
d[a[i]]++;
}
t = 0;
for (i = 1; i <= n; ++i) {//正序计算,然后逆序查看入度为0的节点
if (d[i] == 0) {
d[i] = t;
t = i;
}
}
ans = 0;
process();
fprintf(out, "%lld\n", ans);
fclose(in);
fclose(out);
system("pause");
return 0;
}
/*
8 2
0 1
1 1
2 100
2 100
0 1
5 1
6 2
6 2
*/