HDU 5242 Game 上海大都会赛 G题
比赛的时候写的线段树,结果对这个板理解不够,写错了一直浪费三个小时。还是太菜 T^T
题目描述:
在一颗树上选定K条从根到叶子的路径,求经过的最大的点权和
数据范围 N,K <= 1e5
思路:
贪心.
一个很直观的想法就是每次选取一条点权和最大的路径,然后将这条路径上的点权置零,重复贪心K次得到答案
维护的方法有很多种:
1°排序,树DP每颗子树的最大路径,然后把所有的节点按权值和从大到小排序,
接下来按照排序的顺序贪心,如果当前点的父亲已经枚举过了且没有另外的儿子出现,说明当前点一定是之前某条路径上出现过的点
如果当前点的父亲没有枚举过,或者当前点的父亲已经有儿子枚举过,计算一次答案,计算K次得到答案,具体实现用一个数组累积出现次数
2°优先队列,树DP每颗子树的最大路径,把所有节点放入一个大根堆
每次取出堆顶元素计算一次答案,然后删去堆中这条路径上每一个点,计算K次得到答案
3°DFS序列 + 线段树,DFS根到每个节点的权值和,用DFS序列建一颗树
每次查询一个最大的单点,沿着这个点到根的路径依次删去子树下的DFS序列中每个点的权值(相当于将这条路径上的点权置零)
查询M次得到答案,每个点只会被删去一次,时间复杂度也是O(N·logN)
4°树的轻重链剖分,按权值剖分完后排序取前K条路径,效果和第一种方法是一样的
排序做法:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define foru(i, a, b) for(int i=(a); i<=(b); i++)
#define ford(i, a, b) for(int i=(a); i>=(b); i--)
#define clr(a, b) memset(a, b, sizeof(a));
typedef long long ll;
const int N = 1e5 + 10;
int n, m;
int adj[N], nxt[N], aim[N], tot;
int fa[N], use[N];
ll val[N], f[N];
struct node{
int idx;
ll dp;
}e[N];
void add(int a, int b){
aim[++tot] = b;
nxt[tot] = adj[a];
adj[a] = tot;
}
void dfs(int x){
for(int k = adj[x]; k; k = nxt[k]){
dfs(aim[k]);
f[x] = max(f[x], f[aim[k]]);
}
f[x] += val[x];
}
bool cmp(node A, node B){
return A.dp > B.dp;
}
int main(){
//freopen("G.txt", "r", stdin);
int T, cas = 0; scanf("%d", &T);
while(T--){
tot = 0; clr(adj, 0);
scanf("%d %d", &n, &m);
foru(i, 1, n) scanf("%lld", &val[i]);
foru(i, 2, n){
int a, b; scanf("%d %d", &a, &b);
add(a, b); fa[b] = a;
}
clr(f, 0); dfs(1);
foru(i, 1, n) e[i].idx = i, e[i].dp = f[i];
sort(e + 1, e + 1 + n, cmp);
clr(use, 0); ll ans = 0;
foru(i, 1, n){
int x = e[i].idx; use[x] ++;
if (use[fa[x]] == 1){
use[fa[x]] ++;
continue;
}
m --;
ans += e[i].dp;
if (! m) break;
}
printf("Case #%d: %lld\n", ++cas, ans);
}
return 0;
}
线段树做法:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define foru(i, a, b) for(int i=(a);i<=(b);i++)
#define ford(i, a, b) for(int i=(a);i>=(b);i--)
#define clr(a, b) memset(a, b, sizeof(a));
typedef long long ll;
const int N = 100010;
int n, m;
int adj[N], aim[N], nxt[N], tot;
int st[N], en[N], fa[N], pos[N], tim;
ll val[N], sum[N], Max[4*N], dt[4*N], cnt[N], AIM, ANS;
int L[4*N], R[4*N], mid[4*N], now;
void add(int a, int b){
aim[++tot] = b;
nxt[tot] = adj[a];
adj[a] = tot;
}
void dfs(int x){
st[x] = ++tim;
pos[tim] = x;
cnt[tim] = sum[x];
for(int k = adj[x]; k; k = nxt[k])
sum[aim[k]] = sum[x] + val[aim[k]], dfs(aim[k]);
en[x] = tim;
}
void build(int i, int l, int r){
L[i] = l; R[i] = r;
if (l == r){
Max[i] = cnt[l];
return;
}
mid[i] = (l + r) >> 1;
build(i << 1, l, mid[i]);
build((i<<1)+1, mid[i]+1, r);
Max[i] = max(Max[i<<1], Max[(i<<1)+1]);
}
void query(int i, ll add){
if (L[i] == R[i]){
AIM = Max[i] + add;
now = pos[L[i]];
return;
}
if (Max[i << 1] > Max[(i<<1)+1]) query(i << 1, add + dt[i]);
else query((i << 1) + 1, add + dt[i]);
}
void maintain(int i){
if (R[i] > L[i])
Max[i] = max(Max[i<<1], Max[(i<<1)+1]);
else
Max[i] = cnt[L[i]]; // 初始值
Max[i] += dt[i];
}
void updata(int i, int st, int en, ll add){
if (st <= L[i] && R[i] <= en){
dt[i] += add;
maintain(i);
return;
}
if (st <= mid[i]) updata(i<<1, st, en, add);
if (en > mid[i]) updata((i<<1)+1, st, en, add);
maintain(i);
}
int main(){
freopen("G.txt", "r", stdin);
int T, cas = 0; scanf("%d", &T);
while(T--){
tot = 0; clr(adj, 0);
scanf("%d %d", &n, &m);
foru(i, 1, n) scanf("%lld", &val[i]);
foru(i, 2, n){
int a, b; scanf("%d %d", &a, &b);
add(a, b); fa[b] = a;
}
tim = 0; sum[1] = val[1]; dfs(1);
clr(dt, 0); build(1, 1, n); ANS = 0;
while(m --){
query(1, 0);
if (AIM <= 0) break;
ANS += AIM;
while (now != 0){
if (sum[now] <= 0) break;
updata(1, st[now], en[now], -val[now]);
sum[now] = 0;
now = fa[now];
}
}
printf("Case #%d: %lld\n", ++cas, ANS);
}
return 0;
}