题解:AGC023F 01 on Tree / SS230909A 数据恢复

题解:AGC023F 01 on Tree / SS230909A 数据恢复

他说题目来源是 gym292796J,但是我没有权限???可能是太菜了。http://www.accoders.com/problem.php?cid=4501&pid=0

AtCoder 链接:F - 01 on Tree

题目描述

给定一棵树,树上每个点有非负点权 \(a,b\),求一个操作顺序,使得总是先选父亲在选儿子,同时价值和:每个点选的时候的 \(b\) 乘上未选的点的 \(a\) 的和,求和。最大化价值和。\(n\leq 10^5\)

AGC023F 可以轻易转化为这个问题。

solution

菊花

考虑贪心,使用邻项交换法:假设操作中间有两个点 \((a_1,b_1),(a_2,b_2)\),现在这两个东西的价值是 \(b_1a_2\),swap 一下是 \(b_2a_1\),如果你说他的价值和最大,那么就有 \(b_1a_2>b_2a_1\implies\frac{a_1}{b_1}<\frac{a_2}{b_2}\),欢聚话来说我们只要将所有数字按照 \(a/b\) 排序就好了。

一般树

那么我们还是先按照 \(a/b\) 排序,但是会有一些点违背限制,怎么办呢?我们每次找出全局最小值,如果它的父亲还在,那么将它和父亲合并成一个大点并立刻计算贡献,否则删掉并计算贡献。

因为 \(\frac{a}{b}\leq \frac{a+c}{b+d}\leq\frac{b}{d}\),就是说这样合并以后这个不合法的全局最小值就会跑到后面去,等待以后接受进一步的审判。

合并可以使用并查集,最小值用堆维护。

代码实现

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <numeric>
#include <queue>
#include <set>
#include <vector>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
struct frac {
  LL x, y;
  bool operator<(frac b) const { return x * b.y < b.x * y; }
  frac operator+(frac b) { return frac{x + b.x, y + b.y}; }  // special
  LL operator*(frac b) { return b.x * y; }
};
frac a[1 << 17];
template <int N>
struct dsu {
  int fa[N + 10], siz[N + 10], cnt;
  explicit dsu(int n = N) : cnt(n) {
    iota(fa + 1, fa + n + 1, 1), fill(siz + 1, siz + n + 1, 1);
  }
  int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
  void merge(int x, int y) {
    if (x = find(x), y = find(y), x != y)
      cnt--, fa[y] = x, siz[x] += siz[y], a[x] = a[x] + a[y];
  }
};
int n, fa[1 << 17];
dsu<1 << 17> s;
LL ans = 0;
bool vis[1 << 17];
set<pair<frac, int>> q;
int main() {
  scanf("%d", &n);
  for (int i = 2; i <= n; i++) scanf("%d", &fa[i]);
  for (int i = 1; i <= n; i++) scanf("%lld%lld", &a[i].x, &a[i].y);
  for (int i = 1; i <= n; i++) q.insert({a[i], i});
  vis[0] = 1;
  frac out = {0, 0};
  for (int i = 1; i <= n; i++) {
    int u = q.begin()->second, f = s.find(fa[u]);
    q.erase(q.begin());
    if (vis[f])
      ans += out * a[u], out = out + a[u], vis[u] = 1;
    else {
      q.erase(q.find({a[f], f}));
      ans += a[f] * a[u];
      s.merge(f, u);
      q.insert({a[f], f});
    }
  }
  printf("%lld\n", ans);
  return 0;
}
posted @ 2023-09-09 20:02  caijianhong  阅读(16)  评论(0)    收藏  举报