「SDOI2019」世界地图
询问时假设我们已经知道了左右两边的点的最小生成树长什么样,我们就可以将左右两边合并,再考虑横跨边的贡献。我们直接把两棵树的边拿来跑新的最小生成树。但是复杂度肯定难以承受。
回忆一下,我们有一种往最小生成树上加边的算法:就是找到树上这两点路径上的最长边,看看删掉它会不会更优。我们合并时只会涉及到原树的 个点。这启示我们只维护前/后缀最小生成树的虚树,边权是原树对应路径边权最大值。使用 Kruskal 算法合并时,一开始记录原树所有边的边权和 ,然后我们把边按照新边权从小到大排序,考虑如果已经连通,就让 减去这条边的新边权。最后的 就是新最小生成树边权和。然后我们就可以把加入时两端点已经连通的边忽略掉,在新树上保留关键点了。我们需要保留的关键点是最左边 个和最右边 个。容易发现这样可以 把一个前/后缀拓展,或者进行一次查询。
实现的时候只需要用 std::vector
记下虚树上的边,以及前/后缀最小生成树边权和。合并时先跑 Kruskal,暂时存下新树,并使用 dfs 在 的时间复杂度内得到新树在保留新关键点集的虚树。有点难实现,还有点卡常,所以应当尽量消去时间复杂度里面的 因子。
代码很丑,不建议模仿代码的实现,可能作为理解算法的参考比较好(虽然好像写得难以理解)。
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
using LL = long long;
using uint = unsigned;
using pii = pair<int, int>;
uint SA, SB, SC;
int lim;
inline uint read() {
uint f = 0;
char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) f = f * 10 + (c - '0'), c = getchar();
return f;
}
inline int getweight() {
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
uint t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC % lim + 1;
}
const int N = 105, M = 10005;
int n, m, q, R[N][M], C[N][M];
struct DSU {
int fa[N * M];
inline void reset(int x) {
fa[x] = x;
}
int Find(int x) {
if (fa[x] == x) return x;
return fa[x] = Find(fa[x]);
}
inline void Merge(int x, int y) {
fa[Find(x)] = Find(y);
}
} D;
inline int id(int x, int y) {
return (x - 1) * m + y;
}
struct Edge {
int u, v, w;
inline bool operator<(const Edge o) {
return w < o.w;
}
};
LL f[M], g[M];
vector<Edge> pre[M], suf[M];
vector<pii> G[N * M];
int fa[N * M], fav[N * M], sz[N * M];
bool vis[N * M];
void dfs(int p, int lst) {
fa[p] = lst;
if (sz[p]) vis[p] = 1;
for (auto x : G[p]) {
if (x.fi != lst) {
fav[x.fi] = x.se, dfs(x.fi, p);
if (sz[p] && sz[x.fi]) vis[p] = 1;
sz[p] += sz[x.fi];
}
}
}
inline Edge FindEdge(int x) {
int u = x, z = fav[x];
x = fa[x];
while (!vis[x]) z = max(z, fav[x]), x = fa[x];
return {u, x, z};
}
inline pair<vector<Edge>, LL> MergeTwo(vector<Edge> &x, vector<Edge> &y, vector<int> &mark, LL sum) {
vector<Edge> vec, ret;
vector<int> nodes;
for (auto o : x) vec.push_back(o);
for (auto o : y) vec.push_back(o), sum += o.w;
for (auto o : vec) nodes.push_back(o.u), nodes.push_back(o.v);
sort(nodes.begin(), nodes.end());
nodes.erase(unique(nodes.begin(), nodes.end()), nodes.end());
for (auto o : nodes) D.reset(o);
sort(vec.begin(), vec.end());
for (auto o : vec) {
if (D.Find(o.u) == D.Find(o.v)) sum -= o.w;
else D.Merge(o.u, o.v), G[o.u].push_back({o.v, o.w}), G[o.v].push_back({o.u, o.w});
}
int rt = mark[0];
for (auto o : mark) sz[o] = 1;
dfs(rt, 0);
for (auto o : nodes) if (vis[o] && o != rt) ret.push_back(FindEdge(o));
for (auto o : nodes) sz[o] = 0, vis[o] = 0, vector<pii>().swap(G[o]);
return {ret, sum};
}
inline LL MergeThree(vector<Edge> &x, vector<Edge> &y, vector<Edge> &z, LL sum) {
vector<Edge> vec;
for (auto o : x) vec.push_back(o);
for (auto o : y) vec.push_back(o);
for (auto o : z) vec.push_back(o), sum += o.w;
for (auto o : vec) D.reset(o.u), D.reset(o.v);
sort(vec.begin(), vec.end());
for (auto o : vec) {
if (D.Find(o.u) == D.Find(o.v)) sum -= o.w;
else D.Merge(o.u, o.v);
}
return sum;
}
inline LL solve(int l, int r) {
vector<Edge> tmp;
for (int i = 1; i <= n; i++) tmp.push_back({id(i, 1), id(i, m), R[i][m]});
return MergeThree(pre[l - 1], suf[r + 1], tmp, f[l - 1] + g[r + 1]);
}
int main() {
freopen("in", "r", stdin);
freopen("out", "w", stdout);
n = read(), m = read(), SA = read(), SB = read(), SC = read(), lim = read();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
R[i][j] = getweight();
}
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= m; j++) {
C[i][j] = getweight();
}
}
for (int i = 1; i < n; i++) pre[1].push_back({id(i, 1), id(i + 1, 1), C[i][1]}), f[1] += C[i][1];
for (int i = 2; i <= m; i++) {
vector<Edge> tmp;
vector<int> mark;
for (int j = 1; j <= n; j++) {
mark.push_back(id(j, 1)), mark.push_back({id(j, i)});
tmp.push_back({id(j, i - 1), id(j, i), R[j][i - 1]});
if (j < n) tmp.push_back({id(j, i), id(j + 1, i), C[j][i]});
}
auto e = MergeTwo(pre[i - 1], tmp, mark, f[i - 1]);
pre[i] = e.fi, f[i] = e.se;
}
for (int i = 1; i < n; i++) suf[m].push_back({id(i, m), id(i + 1, m), C[i][m]}), g[m] += C[i][m];
for (int i = m - 1; i; i--) {
vector<Edge> tmp;
vector<int> mark;
for (int j = 1; j <= n; j++) {
mark.push_back(id(j, m)), mark.push_back({id(j, i)});
tmp.push_back({id(j, i + 1), id(j, i), R[j][i]});
if (j < n) tmp.push_back({id(j, i), id(j + 1, i), C[j][i]});
}
auto e = MergeTwo(suf[i + 1], tmp, mark, g[i + 1]);
suf[i] = e.fi, g[i] = e.se;
}
q = read();
while (q--) {
int l = read(), r = read();
printf("%lld\n", solve(l, r));
}
return 0;
}
本文作者:TulipeNoire
本文链接:https://www.cnblogs.com/TulipeNoire/p/18730251/P5360
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步