Edu36
基本情况
尝试板刷Edu的第一天,场出三题,感觉补题困难。
D. Almost Acyclic Graph
暴力的优化技巧
先考虑暴力,对每一条边进行删边,然后跑一遍判环。但是 \(m \leq 10^5\),\(O(m^2)\) 肯定超时。
我们从拓扑排序判环的角度出发,拓扑排序通过减少一个点的入度来模拟删边,而该题的点数 \(n\leq500\)。
所以直接枚举哪个点入度减一会无环即可。
void solve() {
int n, m;
std::cin >> n >> m;
std::vector edge(n, std::vector<int>());
std::vector<int> ind(n);
for (int i = 0, u, v; i < m; i++) {
std::cin >> u >> v;
--u, --v;
edge[u].push_back(v);
ind[v]++;
}
auto save(ind);
for (int i = 0; i < n; i++) {
ind = save;
if (ind[i] == 0) continue;
ind[i]--;
std::queue<int> q;
for (int i = 0; i < n; i++) {
if (ind[i] == 0) {
q.push(i);
}
}
int cnt(0);
while(not q.empty()) {
int fr(q.front()); q.pop(); ++cnt;
for (auto& to : edge[fr]) {
if (--ind[to] == 0) {
q.push(to);
}
}
}
if (cnt == n) {YES; return ;}//如果所有点能构成拓扑序,说明该图无环
}
NO; return ;
}
E. Physical Education Lessons
没想到在这第一次遇到了珂朵莉树。
电子科技大学【ACM】Chtholly Tree (珂朵莉树)
板子
Willem, Chtholly and Seniorious
struct node {//维护三元组[l, r, val]表示该区间内元素相同
int l, r;
mutable i64 val;
node(int L, int R = -1, i64 Val = 0) {
l = L, r = R, val = Val;
}
bool operator<(const node &a) const {
return l < a.l;
}
};
std::set<node> S;
i64 a[100005], n, m, seed, vMax;
i64 rnd() {
i64 ret = seed;
seed = (seed * 7 + 13) % MOD;
return ret;
}
i64 fpow(i64 x, i64 y, i64 mod) {
i64 ans = 1;
x %= mod;
while (y)
{
if (y & 1) {
ans = ans * x % mod;
}
x = x * x % mod;
y >>= 1;
}
return ans;
}
auto split(int pos) {// 目的是把pos所在的点分离出来,珂朵莉树的核心
auto it = S.lower_bound(node(pos));
if (it != S.end() && it->l == pos) {
return it;
}
it--;// 找到pos所在的区间
auto[l, r, val](*it);
S.erase(it);// 把该区间的三元组删掉
S.insert(node(l, pos - 1, val));
return S.insert(node(pos, r, val)).first;// 返回pos所在区间的迭代器
// insert返回pair<iterator, bool>,所以.first
}
void assign(int l, int r, i64 val) {// 区间赋值
auto itR = split(r + 1), itL = split(l);
S.erase(itL, itR);
S.insert(node(l, r, val));
}
void add(int l, int r, i64 val) {// 区间加,用左闭右开区间锁定题目需要操作的区间
auto itR = split(r + 1), itL = split(l);
for (auto it = itL; it != itR; it++) {
it->val += val;// 把每个区间的val都加上
}
}
i64 rank(int l, int r, int k) {// 求第k小,就是用最暴力的方法
std::vector<std::pair<i64, int>> vec;
auto itR = split(r + 1), itL = split(l);
for (auto it = itL; it != itR; it++) {
vec.push_back({it->val, (it->r) - (it->l) + 1});
}
sort(vec.begin(), vec.end());
for (auto[x, y] : vec) {
k -= y;
if (k <= 0) {
return x;
}
}
return -1;
}
i64 sum(int l, int r, int x, int y) {// 求幂次和
i64 ans = 0;
auto itR = split(r + 1), itL = split(l);
for (auto it = itL; it != itR; it++)
ans = (ans + fpow(it->val, x, y) * ((it->r) - (it->l) + 1)) % y;
return ans;
}
本题
比板子还简单,就是区间赋值的同时顺便更新总和即可
struct node {//维护三元组[l, r, val]表示该区间内元素相同
int l, r;
mutable i64 val;
node(int L, int R = -1, i64 Val = 0) {
l = L, r = R, val = Val;
}
bool operator<(const node &a) const {
return l < a.l;
}
};
std::set<node> S;
i64 sum(0);
auto split(int pos) {// 目的是把pos所在的点分离出来,珂朵莉树的核心
auto it = S.lower_bound(node(pos));
if (it != S.end() && it->l == pos) {
return it;
}
it--;// 找到pos所在的区间
auto[l, r, val](*it);
S.erase(it);// 把该区间的三元组删掉
S.insert(node(l, pos - 1, val));
return S.insert(node(pos, r, val)).first;// 返回pos所在区间的迭代器
// insert返回pair<iterator, bool>,所以.first
}
void assign(int l, int r, i64 val) {// 区间赋值
auto itR = split(r + 1), itL = split(l);
for (auto it(itL); it != itR; it++) {
sum -= it->val * (it->r - it->l + 1);//顺便更新sum
}
S.erase(itL, itR);
S.insert(node(l, r, val));
sum += val * (r - l + 1);//顺便更新sum
}
F. Imbalance Value of a Tree
\[\sum^n_{i = 1}\sum^n_{j=1}\operatorname I(x, y)
\]
其中 \(\operatorname I(x, y)\) 表示顶点 \(x\) 到 \(y\) 的简单路径上 \(a_i\) 的最大值和最小值的差。
首先转化原式:
\[\sum^n_{i = 1}\sum^n_{i = 1}\max(i, j) - \sum^n_{i = 1}\sum^n_{i = 1}\min(i, j)
\]
其中,\(\max(x, y)\) 表示顶点 \(x\) 到 \(y\) 的简单路径中 \(a_i\) 的最大值,\(\min(x, y)\) 同理。
然后转化点权为边权,求最大时边权就是两点的最大值,最小时同理。
我们可以想到按照边权大小对边进行升序排序,从低到高对每一条边的两边 \(x, y\) 进行合并,并求出通过此边的路径数量 \(siz_x\times siz_y\),累计贡献 \(siz_x\times siz_y\times \max(a_x, a_y)\)。注意,这里边的权值为边两端节点权值的较大值。
struct DSU {
std::vector<int> f, siz;
DSU() {}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n);
std::iota(f.begin(), f.end(), 0);
siz.assign(n, 1);
}
int find(int x) {
while (x != f[x]) {
x = f[x] = f[f[x]];
}
return x;
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
siz[x] += siz[y];
f[y] = x;
return true;
}
int size(int x) {
return siz[find(x)];
}
};
signed main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n;
std::cin >> n;
std::vector<int> W(n);
for (auto& x : W) std::cin >> x;
std::vector<std::tuple<int, int, int>> edge(n - 1);
for (auto&[u, v, _] : edge) {
std::cin >> u >> v;
--u, --v;
}
i64 ans(0);
auto solve = [&](bool o) {
DSU dsu(n);
for (auto&[u, v, w] : edge) {
w = o ? std::min(W[u], W[v]) : std::max(W[u], W[v]);
}
std::sort(all(edge), [&](auto x1, auto x2) -> bool {
return o ? std::get<2>(x1) > std::get<2>(x2) : std::get<2>(x1) < std::get<2>(x2);
});
for (auto&[u, v, w] : edge) {//从大到小/小到大遍历,显然先进入并查集的是对答案做出贡献的,成为祖宗
auto add(1LL * dsu.size(u) * dsu.size(v) * w);
ans = o ? ans - add: ans + add;
dsu.merge(u, v);
}
};
for (bool o : {false, true}) solve(o);
std::cout << ans << '\n';
return 0;
}