洛谷 P5360 - [SDOI2019]世界地图(最小生成树+虚树)
作者:互联网
好题。
首先看到抠掉一个区间的限制,我们很自然地想到对前后缀跑一遍 MST 后把左右两半的信息合并起来的想法,于是问题转化为怎样维护前后缀的最小生成树。
直接做复杂度 \(nm^2\log n\),显然无法通过,乍一眼貌似也需要可持久化 LCT / 树状数组套 LCT 等奇奇怪怪的数据结构才能优化,看上去异常棘手。但是别忘了,我们还没有用到“图是一张网格图”的性质。我们注意到,这题列数 \(m\) 很多但行数 \(n\) 很小,因此我们肯定尽量将复杂度倾向于 \(n\)。可以发现当我们新增扩展一列 \(i\) 时,我们新增的边只会连在 \(\mathcal O(n)\) 个点之间,即所有形如 \((j,i-1),1\le j\le m\) 的点。按照 LCT 维护最小生成树的那套理论,当我们新加入一条边 \(E=(u,v,w)\) 时,最小生成树的变化可以表现为,取出 \(u,v\) 路径上权值最大的边 \(E_0\),如果 \(E_0\) 的权值 \(>w\) 则删除 \(E_0\) 加入 \(E\),否则就什么也不干。也就是说,在我们这一轮扩展中,只有这 \(n\) 个关键点两两路径上权值最大的边可能会在这一轮扩展中被删除,而这样的边最多只有 \(\mathcal O(n)\) 个,因为如果我们对 \(n\) 个关键点建虚树,那么这样的边都肯定虚树上某条链上权值最大的边,而虚树上边数最大为 \(2n-2\)。
因此我们考虑不记录整个最小生成树的边集,而只记录这些“关键边”组成的集合,对于剩余的在 MST 上的边,无论我们怎么扩展,它们肯定都会在 MST 上,因此我们只用单纯地记录一下它们的边权之和即可。直接记录这些边在原图中的编号则会导致 kruskal 的结果出错,因此我们不能直接记录这些边在原图上的编号,改进方法是,我们找出 \((1,1),(2,1),(3,1),\cdots,(n,1),(1,i-1),(2,i-1),\cdots,(n,i-1)\) 这些点在 \(1\sim i-1\) 上的虚树,然后对虚树上 \(\mathcal O(n)\) 个点重标号并对虚树上每条链求出权值最大的边,这样新增一列时,我们将新增的边与原来 \(\mathcal O(n)\) 条边放在一起跑 kruskal,建出这 \(9n\) 个点的最小生成树后再建出以 \((1,1),(2,1),\cdots,(n,1),(1,i),(2,i),\cdots,(n,i)\) 为关键点的虚树,求出每条链上权值最大的边作为新的关键边集合即可实现合并两棵 MST 的过程。
时间复杂度 \(n(m+q)\log n\),部分不清楚的地方可通过阅读代码理解。
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
*/
标签:dfs,洛谷,int,MST,tot,pb,P5360,vec,SDOI2019 来源: https://www.cnblogs.com/ET2006/p/luogu-P5360.html