洛谷 P5360 - [SDOI2019]世界地图(最小生成树+虚树)
好题。
首先看到抠掉一个区间的限制,我们很自然地想到对前后缀跑一遍 MST 后把左右两半的信息合并起来的想法,于是问题转化为怎样维护前后缀的最小生成树。
直接做复杂度 ,显然无法通过,乍一眼貌似也需要可持久化 LCT / 树状数组套 LCT 等奇奇怪怪的数据结构才能优化,看上去异常棘手。但是别忘了,我们还没有用到“图是一张网格图”的性质。我们注意到,这题列数 很多但行数 很小,因此我们肯定尽量将复杂度倾向于 。可以发现当我们新增扩展一列 时,我们新增的边只会连在 个点之间,即所有形如 的点。按照 LCT 维护最小生成树的那套理论,当我们新加入一条边 时,最小生成树的变化可以表现为,取出 路径上权值最大的边 ,如果 的权值 则删除 加入 ,否则就什么也不干。也就是说,在我们这一轮扩展中,只有这 个关键点两两路径上权值最大的边可能会在这一轮扩展中被删除,而这样的边最多只有 个,因为如果我们对 个关键点建虚树,那么这样的边都肯定虚树上某条链上权值最大的边,而虚树上边数最大为 。
因此我们考虑不记录整个最小生成树的边集,而只记录这些“关键边”组成的集合,对于剩余的在 MST 上的边,无论我们怎么扩展,它们肯定都会在 MST 上,因此我们只用单纯地记录一下它们的边权之和即可。直接记录这些边在原图中的编号则会导致 kruskal 的结果出错,因此我们不能直接记录这些边在原图上的编号,改进方法是,我们找出 这些点在 上的虚树,然后对虚树上 个点重标号并对虚树上每条链求出权值最大的边,这样新增一列时,我们将新增的边与原来 条边放在一起跑 kruskal,建出这 个点的最小生成树后再建出以 为关键点的虚树,求出每条链上权值最大的边作为新的关键边集合即可实现合并两棵 MST 的过程。
时间复杂度 ,部分不清楚的地方可通过阅读代码理解。
const int MAXN = 100; const int MAXM = 1e4; const int MAXC = MAXN << 4; int n, m, hor[MAXN + 5][MAXM + 5], vert[MAXN + 5][MAXM + 5]; u32 SA, SB, SC; int lim; int getweight() { SA ^= SA << 16; SA ^= SA >> 5; SA ^= SA << 1; unsigned int t = SA; SA = SB; SB = SC; SC ^= t ^ SA; return SC % lim + 1; } struct edge { int u, v, w; edge(int _u = 0, int _v = 0, int _w = 0): u(_u), v(_v), w(_w) {} bool operator < (const edge &rhs) {return w < rhs.w;} }; struct dsu { int f[MAXC + 5]; void init() {memset(f, 0, sizeof(f));} int find(int x) {return (!f[x]) ? x : f[x] = find(f[x]);} bool merge(int x, int y) {x = find(x); y = find(y); return (x == y) ? 0 : (f[x] = y, 1);} } F; struct graph { int hd[MAXC + 5], nxt[MAXC * 2 + 5], to[MAXC * 2 + 5], val[MAXC * 2 + 5], ec = 0; void init() {memset(hd, 0, sizeof(hd)); ec = 0;} void adde(int u, int v, int w) { to[++ec] = v; val[ec] = w; nxt[ec] = hd[u]; hd[u] = ec; to[++ec] = u; val[ec] = w; nxt[ec] = hd[v]; hd[v] = ec; } } G; bool is[MAXC + 5], ont[MAXC + 5]; struct MST { vector<edge> E; int tot; ll static_sum; // sum of static edges void init(vector<int> w) { tot = n; for (int i = 0; i < w.size(); i++) E.pb(edge(i + 1, i + 2, w[i])); } ll query() { ll sum = static_sum; for (int i = 0; i < E.size(); i++) sum += E[i].w; return sum; } } pre[MAXM + 5], suf[MAXM + 5]; int fa[MAXC + 5], faw[MAXC + 5]; void dfs_init(int x, int f) { fa[x] = f; for (int e = G.hd[x]; e; e = G.nxt[e]) { int y = G.to[e], z = G.val[e]; if (y == f) continue; faw[y] = z; dfs_init(y, x); } } int id[MAXC + 5], idcnt = 0; vector<pii> te; int dfs_build(int x, int f) { int V = 0, two = 0; ont[x] = is[x]; for (int e = G.hd[x]; e; e = G.nxt[e]) { int y = G.to[e]; if (y == f) continue; int z = dfs_build(y, x); if (z) { if (V) two = 1, te.pb(mp(x, z)); else V = z; } } if (!V) return (is[x]) ? x : 0; else { ont[x] = 1; if (two) return is[x] = 1, te.pb(mp(x, V)), x; else { if (is[x]) return te.pb(mp(x, V)), x; else return V; } } } int qrymx(int u, int v) { int mx = 0; while (v ^ u) chkmax(mx, faw[v]), v = fa[v]; return mx; } MST merge(MST &a, MST &b, vector<int> w) { vector<edge> ve; MST c; c.tot = a.tot + b.tot; for (int i = 0; i < a.E.size(); i++) ve.pb(a.E[i]); for (int i = 0; i < b.E.size(); i++) ve.pb(edge(b.E[i].u + a.tot, b.E[i].v + a.tot, b.E[i].w)); for (int i = 1; i <= n; i++) ve.pb(edge(a.tot - n + i, a.tot + i, w[i - 1])); F.init(); G.init(); ll esum = 0; sort(ve.begin(), ve.end()); for (int i = 0; i < ve.size(); i++) if (F.merge(ve[i].u, ve[i].v)) G.adde(ve[i].u, ve[i].v, ve[i].w), esum += ve[i].w; memset(is, 0, sizeof(is)); memset(ont, 0, sizeof(ont)); for (int i = 1; i <= c.tot; i++) is[i] = (i <= n || i > c.tot - n); dfs_init(1, 0); te.clear(); dfs_build(1, 0); idcnt = 0; for (int i = 1; i <= c.tot; i++) if (ont[i]) id[i] = ++idcnt; for (pii p : te) c.E.pb(edge(id[p.fi], id[p.se], qrymx(p.fi, p.se))); for (int i = 0; i < c.E.size(); i++) esum -= c.E[i].w; c.static_sum = a.static_sum + b.static_sum + esum; c.tot = idcnt; return c; } int main() { scanf("%d%d%u%u%u%d", &n, &m, &SA, &SB, &SC, &lim); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) hor[i][j] = getweight(); for (int i = 1; i < n; i++) for (int j = 1; j <= m; j++) vert[i][j] = getweight(); // printf("hor:\n"); // for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) // printf("%d%c", hor[i][j], " \n"[j == m]); // printf("vert:\n"); // for (int i = 1; i < n; i++) for (int j = 1; j <= m; j++) // printf("%d%c", vert[i][j], " \n"[j == m]); for (int j = 1; j <= m; j++) { vector<int> vec; for (int i = 1; i < n; i++) vec.pb(vert[i][j]); pre[j].init(vec); suf[j].init(vec); } for (int i = 2; i <= m; i++) { vector<int> vec; for (int j = 1; j <= n; j++) vec.pb(hor[j][i - 1]); pre[i] = merge(pre[i - 1], pre[i], vec); } for (int i = m - 1; i; i--) { vector<int> vec; for (int j = 1; j <= n; j++) vec.pb(hor[j][i]); suf[i] = merge(suf[i], suf[i + 1], vec); } // printf("pre:\n"); // for (int i = 1; i <= m; i++) { // printf("[1, %d]:\n", i); // for (int j = 0; j < pre[i].E.size(); j++) // printf("%d %d %d\n", pre[i].E[j].u, pre[i].E[j].v, pre[i].E[j].w); // printf("weight of MST: %lld\n", pre[i].query()); // } int qu; scanf("%d", &qu); while (qu--) { int l, r; scanf("%d%d", &l, &r); vector<int> vec; for (int j = 1; j <= n; j++) vec.pb(hor[j][m]); printf("%lld\n", merge(suf[r + 1], pre[l - 1], vec).query()); } return 0; } /* 6 5 998244353 1004535809 1000000007 5 6 2 2 2 3 2 4 3 3 3 4 4 4 */
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现