逐月黯月杯
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;
}