逐月黯月杯

C. 服务器选址

题目描述

\(N\) 个房间,有 \(M\) 条网线连接他们,第 \(i\) 条连接 \(u_i,v_i\),延迟为 \(w_i\)。有 \(p\) 个房间需要网络,你可以在 \(k\) 个房间建立一台服务器,并为 \(p\) 个房间确定一条到某个服务器的线路,其延迟为路径上延迟最大值。

对于 \(\forall 1\le k\le N\),找到所有 \(p\) 个房间延迟之和的最小值。

思路

考虑贪心。

每次我们一定会选择当前最优的房间建立服务器,所以我们来想怎么求出在每个点建立服务器的贡献。

由于这道题延迟为最大值,所以考虑 kruskal 重构树。建完重构树后,我们看每个非叶子节点(即原图中的边)的贡献。若一段有服务器,而另一端没有,那么有另一端的每个点都要跑到对面去,统计贡献即可。

空间复杂度 \(O(N+M)\),时间复杂度 \(O(M\log M+N^2)\)

代码

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

const int MAXN = 10001, MAXM = 12497501;

struct Edge {
  int u, v, w;
}g[MAXM];

int t, n, m, p, cnt[MAXN], f[MAXN], a[MAXN], tot, fa[MAXN];
ll c[MAXN], ans;
bool vis[MAXN];
vector<int> e[MAXN];

int getfa(int u) {
  return (f[u] == u ? u : f[u] = getfa(f[u]));
}

void dfs(int u, ll sum, bool op) {
  if(u <= n) {
    c[u] = sum;
    return;
  }
  for(int v : e[u]) {
    dfs(v, sum - 1ll * (op && !vis[v]) * cnt[v] * a[u] + 1ll * (!vis[u]) * (cnt[u] - cnt[v]) * a[u], op & vis[v]);
  }
}

void DFS(int u, ll sum) {
  if(u <= n) {
    c[u] = sum;
    return;
  }
  for(int v : e[u]) {
    fa[v] = u;
    DFS(v, sum + 1ll * (cnt[u] - cnt[v]) * a[u]);
  }
}

void Solve() {
  cin >> n >> m >> p;
  for(int i = 1; i <= 2 * n; ++i) {
    cnt[i] = vis[i] = 0;
    f[i] = i;
    e[i].clear();
  }
  for(int i = 1, x; i <= p; ++i) {
    cin >> x;
    cnt[x]++;
  }
  for(int i = 1; i <= m; ++i) {
    cin >> g[i].u >> g[i].v >> g[i].w;
  }
  sort(g + 1, g + m + 1, [](const Edge &a, const Edge &b) -> bool {
    return a.w < b.w;
  });
  tot = n;
  for(int i = 1; i <= m; ++i) {
    int u = getfa(g[i].u), v = getfa(g[i].v), w = g[i].w;
    if(u != v) {
      e[++tot].emplace_back(u), e[tot].emplace_back(v);
      cnt[tot] = cnt[u] + cnt[v];
      a[tot] = w;
      f[u] = f[v] = tot;
    }
  }
  DFS(tot, 0);
  ans = 0;
  for(int i = 1; i <= n; ++i) {
    ll Min = LLONG_MAX, u;
    for(int j = 1; j <= n; ++j) {
      if(c[j] < Min) {
        Min = c[j], u = j;
      }
    }
    ans += Min;
    for(; u; u = fa[u]) {
      vis[u] = 1;
    }
    cout << ans << " \n"[i == n];
    dfs(tot, 0, 1);
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  for(cin >> t; t--; Solve()) {
  }
  return 0;
}

D. 狩猎

题目描述

给定一个以 \(r\) 为根的树,树上每个点都有一个权值 \(a_i\)。我们定义 \(f(i)\) 为在 \(i\) 的子树内选择出不同的结点权值异或和之和。请对于 \(1\le r\le N\) 求出以 \(r\) 为根时所有 \(f(i)\) 之和。

思路

看到异或首先想到线性基。我们先考虑怎么求出不同异或值之和。

对于每一位,我们随便抽出一个包含这一位的一个数。那么剩下的数,如果不包含这一位那么就随便选,如果包含这一位的数选择了奇数个,那么抽出的数就不选,否则选。也就是除了选出的数其他随便选。那么能凑出来数的总和为线性基中包含哪些位乘以线性基大小减一。

求子树内的线性基很容易,所以我们考虑换根。每次从父亲换到儿子时,我们可以预处理出该父亲所有儿子前、后缀组成的线性基,将前后缀拼起来即可。

空间复杂度 \(O(N\log N)\),时间复杂度 \(O(N\log ^2 N)\)

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;

const int MAXN = 200001, MOD = 998244353;

int Pow(int a, int b) {
  int ret = 1;
  for(; b; a = 1ll * a * a % MOD, b >>= 1) {
    if(b & 1) {
      ret = 1ll * ret * a % MOD;
    }
  }
  return ret;
}

struct Xor {
  int d[30];
  void Clear() {
    for(int i = 29; i >= 0; --i) {
      d[i] = 0;
    }
  }
  bool Insert(int x) {
    for(int i = 29; i >= 0; --i) {
      if((x >> i) & 1) {
        if(!d[i]) {
          d[i] = x;
          return 1;
        }
        x ^= d[i];
      }
    }
    return 0;
  }
  bool Insert(const Xor &x) {
    bool op = 0;
    for(int i = 29; i >= 0; --i) {
      if(x.d[i]) {
        op |= Insert(x.d[i]);
      }
    }
    return op;
  }
  int Get() {
    int ret = 0, cnt = 0;
    for(int i = 29; i >= 0; --i) {
      ret |= d[i];
      cnt += (d[i] > 0);
    }
    return 1ll * ret * Pow(2, max(0, cnt - 1)) % MOD;
  }
  Xor operator=(const Xor &x) {
    for(int i = 29; i >= 0; --i) {
      d[i] = x.d[i];
    }
    return x;
  }
}d[MAXN], f[MAXN], r[MAXN];

int n, a[MAXN], sum, ans[MAXN];
vector<int> e[MAXN], son[MAXN];
vector<Xor> pre[MAXN], suf[MAXN];

void Insert(int x) {
  sum = (sum + x) % MOD;
}

void Erase(int x) {
  sum = (sum - x + MOD) % MOD;
}

void dfs(int u, int fa) {
  d[u].Insert(a[u]);
  for(int v : e[u]) {
    if(v != fa) {
      dfs(v, u);
      d[u].Insert(d[v]);
      son[u].emplace_back(v);
    }
  }
  Insert(d[u].Get());
  pre[u].resize(son[u].size()), suf[u].resize(son[u].size());
  for(int i = 0; i < son[u].size(); ++i) {
    if(i) {
      pre[u][i] = pre[u][i - 1];
    }
    pre[u][i].Insert(d[son[u][i]]);
  }
  for(int i = int(son[u].size()) - 1; i >= 0; --i) {
    if(i < int(son[u].size()) - 1) {
      suf[u][i] = suf[u][i + 1];
    }
    suf[u][i].Insert(d[son[u][i]]);
  }
}

void DFS(int u, int fa) {
  ans[u] = sum;
  Erase(r[u].Get());
  int i = 0;
  for(int v : e[u]) {
    if(v != fa) {
      f[u] = f[fa];
      f[u].Insert(a[u]);
      if(i) {
        f[u].Insert(pre[u][i - 1]);
      }
      if(i < int(son[u].size()) - 1) {
        f[u].Insert(suf[u][i + 1]);
      }
      Insert(f[u].Get());
      r[v] = d[v];
      r[v].Insert(f[u]);
      Insert(r[v].Get());
      Erase(d[v].Get());
      DFS(v, u);
      Erase(f[u].Get());
      Insert(d[v].Get());
      i++;
    }
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1, u, v; i < n; ++i) {
    cin >> u >> v;
    e[u].emplace_back(v);
    e[v].emplace_back(u);
  }
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
  }
  dfs(1, 0);
  r[1] = d[1];
  DFS(1, 0);
  for(int i = 1; i <= n; ++i) {
    cout << ans[i] << " ";
  }
  return 0;
}
posted @ 2024-10-22 19:56  Yaosicheng124  阅读(7)  评论(0编辑  收藏  举报