网络小说(novel)

1|0题目描述:

给定一个 n 个点,m 条边的无向图,点有点权,边有边权,定义两个点之间的距离为两点间的所有路径中的边权的最大值的最小值。

现在问你,所有点权差不小于 d 的点对之间的距离和。

n2×105m5×105

2|0解题思路:

首先注意到,题目定义的距离为 两点间的所有路径中的边权的最大值的最小值 那么这就启发我们想到 kruskal 重构树

有一个朴素想法,先建出重构树,然后枚举合法点对,求他们在树上的 LCA,时间复杂度为 O(n2logn)

考虑优化,注意到其实我们不需要枚举点对,直接计算每个重构树上的非叶子节点的贡献即可。

因为 kruskal 重构树是一个二叉树,所以只需要计算其中一个子树对另外一个子树的贡献,那么这就可以想到树上启发式合并。

具体做法就是先建出重构树,对于一个非叶子节点,我们先递归小子树,然后递归大子树,将两个子树内部的贡献算好,同时在我们递归大子树时,将其的叶子结点放入树状数组。然后再次递归小子树,这时候我们计算的是两个子树间有多少合法点对,这我们直接在树状数组上查找即可。

时间复杂度为 O(nlog2n)


重构树的具体实现不再赘述,树上启发式合并再多嘴几句,我们在递归时打个标记表示这个子树要不要保留,假设这一整个子树都要保留那么我们将小子树的叶子结点也加入树状数组,否则我们删除大子树的所有叶子结点。

3|0代码实现:

#include<bits/stdc++.h> #define endl '\n' #define int long long using namespace std; const int N = 2e6 + 10; int n, m, d, tot, cnt; int fa[N], c[N], a[N], tmp[N]; int ls[N], rs[N], l[N], r[N], siz[N]; struct Edge{ int x, y, w; bool operator < (const Edge A){ return w < A.w; } }e[N]; struct BIT{//树状数组 int t[N]; int lowbit(int x){ return x & (-x); } void update(int x, int val){ for(int i = x; i <= cnt; i += lowbit(i)) t[i] += val; } int query(int x){ int res = 0; for(int i = x; i; i -= lowbit(i)) res += t[i]; return res; } }tr; void lsh(){ sort(tmp + 1, tmp + 1 + n); cnt = unique(tmp + 1, tmp + 1 + n) - tmp - 1; for(int i = 1; i <= n; i++) c[i] = lower_bound(tmp + 1, tmp + 1 + cnt, c[i]) - tmp; } int find(int x){//并查集 if(x == fa[x]) return x; return fa[x] = find(fa[x]); } void merge(int x, int y){ fa[y] = x; siz[x] += siz[y]; } void kruskal(){//重构树 tot = n; for(int i = 1; i <= m; i++){ int x = e[i].x, y = e[i].y, w = e[i].w; if(find(x) == find(y)) continue; a[++tot] = w; x = find(x), y = find(y); if(siz[x] > siz[y]) ls[tot] = y, rs[tot] = x; else ls[tot] = x, rs[tot] = y; merge(tot, x), merge(tot, y); } } int dfs1(int x){ int res = 0; if(x <= n) return tr.query(cnt) - tr.query(r[c[x]] - 1 + (!d)) + tr.query(l[c[x]]); res = dfs1(ls[x]) + dfs1(rs[x]); return res; } void update(int x, int val){ if(x <= n){ tr.update(c[x], val); return; } update(ls[x], val), update(rs[x], val); } __int128 ans;//出题人Milmon的毒瘤数据 void dfs(int x, bool del){//这里和题解不太一样,我这里记的是是否删除这个子树,但本质是一样的 if(x <= n){ if(!del) tr.update(c[x], 1); return; } dfs(ls[x], 1);//先递归小子树 dfs(rs[x], 0);//再递归大子树 ans += dfs1(ls[x]) * a[x];//这里再次递归小子树计算贡献 if(del) update(rs[x], -1);//更新树状数组 else update(ls[x], 1); } char ch[20]; int top; void write() { if(!ans) ch[++top] = 0; while(ans != 0) ch[++top] = ans % 10, ans /= 10; while(top) putchar(ch[top--] + '0'); putchar('\n'); return; } signed main(){ freopen("novel.in", "r", stdin); freopen("novel.out", "w", stdout); ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n >> m >> d; for(int i = 1; i <= n; i++) cin >> c[i], tmp[i] = c[i]; lsh();//因为要使用的是权值树状数组所以需要离散化 for(int lst = 0, i = 1; i <= cnt; i++){//求出符合答案的前缀 while(lst + 1 <= i && tmp[i] - tmp[lst + 1] >= d) lst++; l[i] = lst; } for(int lst = cnt + 1, i = cnt; i >= 1; i--){//求出符合答案的后缀 while(lst - 1 >= i && tmp[lst - 1] - tmp[i] >= d) lst--; r[i] = lst; } for(int i = 1; i <= 2 * n; i++) fa[i] = i, siz[i] = 1; for(int i = 1; i <= m; i++) cin >> e[i].x >> e[i].y >> e[i].w; sort(e + 1, e + 1 + m); kruskal(); dfs(find(1), 1); write(); return 0; } /* 4 5 2 6 4 5 2 1 2 8 2 3 7 2 4 8 1 3 2 1 4 1 */

__EOF__

本文作者huangweiliang
本文链接https://www.cnblogs.com/huangweiliang/p/18493672.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   _huangweiliang  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示