题解 | #小 Q 与树#

小 Q 与树

https://ac.nowcoder.com/acm/contest/11171/D

给出一颗树,结点权值为 求:

思路

本题为点分治模板题

以重心为根,用 solve(x) 解决 子树内贡献

每次 solve(x) 时,首先得到 经过该点不经过该点 的贡献总和 calc(x, fa, 0)

这个过程首先利用 dfs_dis(x, fa, 0) 得到以 为根的链信息再将链两两合并,得到 的路径贡献

排除 不经过该点, 即排除 的情况,只需要 先向下走一步,然后统计答案,及 calc(x, fa, 1)

注意到以 为根后会将 删去,则每条路径有且仅会被统计一次,故答案正确。

对于本题,由于求 时若暴力枚举,calc 复杂度会为 总复杂度为

需要先排序处理,calc 复杂度为 总复杂度为

注意: 求重心时 S = sz[x] 每次都要更新,否则复杂度不对

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

#define rep(i, s, t) for (int i = (int)(s); i <= (int)(t); ++i)

const int mod = 998244353;

int root, mx[N], sz[N];
int n, m, S, tot;
bool vis[N];
vector<int> G[N];

bool chkmax(int &x, int y) {
  if (x < y) return x = y, 1; return false;
}

int read() {
  int x = 0, ch = getchar();
  while (ch < '0' || ch > '9') ch = getchar();
  while (ch >='0' && ch <='9')
    x = x * 10 + (ch ^ 48), ch = getchar();
  return x;
}

#define int long long
int a[N], ans;
pair<int, int> q[(int)(1e6) + 10];

void dfs_root(int x, int fa) {
  mx[x] = 0, sz[x] = 1;
  for (int y : G[x]) {
    if (y == fa || vis[y]) continue;
    dfs_root(y, x);
    sz[x] += sz[y];
    chkmax(mx[x], sz[y]);
  }
  chkmax(mx[x], S - sz[x]);
  if (mx[x] < mx[root]) root = x;
}

void dfs_dis(int x, int fa, int dis) {
  q[++ tot] = make_pair(a[x], dis);
  for (int y : G[x]) {
    if (y == fa || vis[y]) continue;
    dfs_dis(y, x, dis + 1);
  }
}

int Mod(int x) {
  return (x % mod + mod) % mod;
}

int calc(int x, int fa, int org) {
  int res = 0;
  tot = 0;
  dfs_dis(x, fa, org);
  sort(q + 1, q + tot + 1);
  int dis_sum = 0, dis_prefix = 0;
  rep(i, 1, tot) {
    dis_sum += q[i].second;
  }
  rep(i, 1, tot) {
    dis_prefix += q[i].second;
    res = Mod(res + q[i].first * (dis_sum - dis_prefix));
    res = Mod(res + (tot - i) * q[i].second * q[i].first);
  }
  return Mod(res + res);
}

void solve(int x) {
  tot = 0;
  vis[x] = 1;
  ans += calc(x, 0, 0);
  for (int y : G[x]) {
    if (vis[y]) continue;
    ans = Mod(ans - calc(y, x, 1));
    mx[root = 0] = S = n;
    S = sz[x];
    dfs_root(y, x);
    solve(root);
  }
}

signed main() {
  n = read();
  rep(i, 1, n) a[i] = read();
  rep(i, 1, n - 1) {
    int x = read(), y = read();
    G[x].push_back(y);
    G[y].push_back(x);
  }
  mx[root = 0] = S = n;
  dfs_root(1, 0);
  solve(root);
  printf("%lld", ans);
}
全部评论

相关推荐

HR_丸山彩同学:你的项目描述里,系统设计讲了很多:MemCube是什么、三级存储架构怎么设计、四种遗忘策略分别是什么。这些面试的时候讲没问题,但简历上不需要这么细。 简历要突出的是影响力,不是实现细节。面试官看简历的时候想知道的是「这个项目有多大价值」,不是「这个项目具体怎么实现的」。实现细节是面试时候聊的 怎么改:技术细节可以精简为一句「采用三级存储架构+四种遗忘策略」,把省出来的篇幅用来写影响力。比如:项目有没有开源?有没有写成技术博客?有没有被别人使用过? 校园经历没有任何信息量,任何人都可以写这句话,写了等于没写。更关键的是,你投的是技术岗,校园活动经历本来就不是加分项。如果非要写,必须写出具体的数字和成果。如果你没有这些数字,那就老老实实删掉 「端到端耗时缩减30-40%」要给出确切数字和绝对值。从1000ms降到600ms是降了40%,从100ms降到60ms也是降了40%,但这两个含义完全不一样。其他也是,涉及到数据,准备好证据,口径统一,面试会问 「熟练」「熟悉」「了解」混在一起用,读起来很乱。而且「了解前端需求」最好改成「具备前后端协作经验」
点赞 评论 收藏
分享
评论
2
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务