[SDOI 2019]世界地图[虚树][MST]

菜啊,被迫文化课还天天颓,不知道过了多久才补上这篇题解,但是 zhq 讲的真好

虚树相关知识


简述题意:给定一张 $n\times m$ 的网格图,其中最左端的一排点和最右端的一排点再相连,形成一个圆柱形网格图,边有边权。可以将横纵线理解为经纬线

然后有 q 次询问,每次删除经度在 $[l, r]$ 这个区间的图,求剩下部分的 MST(最小生成树)

数据范围:$n\leq 100, m\leq 10000, q\leq 10000$


可以想到,对于一次询问的图,其实就是 以第一排点开始的某个前缀图 + 以第 m 排点开始的某个后缀图 + 第 1 排点与第 m 排点相连的边 形成的一张图的 MST

求出每一个前缀图的 MST,再求出每一个后缀图的 MST,加上 $1\rightarrow m$ 的那一排边,这样会形成很多环,来考虑怎样将这些环搞掉

因为这些图是网格图,所以对于一个环,只需要删除掉这个环上边权最大的边即可

但是这样显然复杂度爆炸,实际上可能被删除的边有 $O(N^2)$ 条?其实是 $O(N)$ 条

来考虑虚树

对于前缀图,我们将第 1 排点全部设为 key 点;对于后缀图,我们将第 m 排点全部设为 key 点,在 MST 上形成一棵虚树

这样的一棵虚树的一条边表示原图 MST 上的一条链

可以从构造上发现,对于这样的一棵虚树,它只有 $O(N)$ 条边和 $O(N)$ 个点

(原因是因为每个非叶子节点都至少有两个儿子,如同 SAM 只有 O(N) 个点的原因 —— zhq)

这样我们虚树上的每一条边表示它所代表的那一条链上边权最大的边的边权

然后对于整张图(前缀虚树+后缀虚树+一排边)求一遍 MST 即可

需要注意的是,我们是在虚树上求 MST,所以这个 MST 并不是答案,原树上还有不少不可能被删除的边没有被统计

那我们可以额外记录一下,不可能被删除的边的边权和

可能被删的边只有虚树上的边以及那一排边

那么最后答案就是 = 前缀图中一定不可能被删的边权和 + 后缀图中一定不可能被删的边权和 + (前缀虚树+后缀虚树+一排边 的 MST)

好像做完了

但是还有个问题没有解决:怎样求出前缀图虚树和后缀图虚树

以前缀为例子:假设我们已经求出了 i-1 的前缀图的虚树,考虑如何求出 i 的前缀图的虚树

其实和上面计算答案的方法一样,把 i-1 的那个前缀图当作计算答案时的后缀图,第 i 排点是计算答案时的前缀图,再加上 $(i-1)\rightarrow i$ 那一排边

按照上面计算答案的算法做,即可完成。后缀同理

另外需要注意的是,这道题我(zhq)所用的建造虚树的方法并不适用于所有虚树构造

复杂度:大概是 $O(nm+qn)$?

代码(适当的加了几个注释):

  1 // #3112. 「SDOI2019」世界地图
  2 // MST + 虚树
  3 
  4 #include <ctime>
  5 #include <cmath>
  6 #include <cstdio>
  7 #include <cstring>
  8 #include <cstdlib>
  9 #include <exception>
 10 #include <iostream>
 11 #include <algorithm>
 12 #include <vector>
 13 #include <queue>
 14 #define inf 10010
 15 #define N 110
 16 #define INF 0x7fffffff
 17 #define ll long long
 18 
 19 namespace chiaro{
 20 
 21 template <class I>
 22 inline void read(I &num){
 23     num = 0; char c = getchar(), up = c;
 24     while(c < '0' || c > '9') up = c, c = getchar();
 25     while(c >= '0' && c <= '9') num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar();
 26     up == '-' ? num = -num : 0; return;
 27 }
 28 template <class I>
 29 inline void read(I &a, I &b) {read(a); read(b);}
 30 template <class I>
 31 inline void read(I &a, I &b, I &c) {read(a); read(b); read(c);}
 32 
 33 struct MSTedge {
 34     int from;
 35     int to;
 36     int val;
 37     MSTedge() {}
 38     MSTedge(int from, int to, int val) {
 39         this->from = from;
 40         this->to = to;
 41         this->val = val;
 42     }
 43 };
 44 
 45 struct edge {
 46     int to;
 47     int val;
 48     edge* next;
 49 };
 50 
 51 int n, m;
 52 ll ansdis;
 53 int id[N][inf]; // id
 54 int right[N][inf], down[N][inf]; // 向右边权 & 向下边权
 55 bool isKey[inf * N]; // 是否是 key 点
 56 edge* g[inf * N];
 57 
 58 int fa[inf * N];
 59 
 60 struct MST {
 61     ll sum; // MST 上不可能被删的边的边权之和
 62     std::vector <MSTedge> e;
 63 
 64     MST() {}
 65     MST(int now) { // 搞出 now 那一列点的 MST
 66         sum = 0;
 67         for(int i = 1; i < n; i++) 
 68             e.push_back(MSTedge(id[i][now], id[i + 1][now], down[i][now]));
 69         return;
 70     }
 71 
 72     inline int size() {return e.size();}
 73 };
 74 
 75 MST pre[inf], suf[inf]; // 前缀 & 后缀
 76 
 77 bool edgeflag;
 78 inline void connect(int from, int to, int val){
 79     static edge pool[inf * N];
 80     static edge* p = pool;
 81     if(edgeflag) p = pool, edgeflag = 0; // 清空
 82     p->to = to;
 83     p->val = val;
 84     p->next = g[from];
 85     g[from] = p; p++;
 86     return;
 87 }
 88 
 89 namespace gen {
 90     unsigned int SA, SB, SC;int lim;
 91     int getweight() {
 92         SA ^= SA << 16;
 93         SA ^= SA >> 5;
 94         SA ^= SA << 1;
 95         unsigned int t = SA;
 96         SA = SB;
 97         SB = SC;
 98         SC ^= t ^ SA;
 99         return SC % lim + 1;
100     }
101 
102     void gen() {
103         read(n, m);
104         read(SA, SB, SC);
105         read(lim);
106         int i, j, w;
107         for(i = 1; i <= n; i++)
108             for(j = 1; j <= m; j++) {
109                 w = getweight();
110                 right[i][j] = w;
111             }
112         for(i = 1; i < n; i++)
113             for(j = 1; j <= m; j++) {
114                 w = getweight();
115                 down[i][j] = w;
116             }
117         int index = 0;
118         for(int i = 1; i <= n; i++) {
119             for(int j = 1; j <= m; j++) id[i][j] = ++index;
120         }
121         return;
122     }
123 }
124 
125 int findf(int f) {return ((f == fa[f]) ? (f) : (fa[f] = findf(fa[f])));}
126 
127 inline void clear(const std::vector <MSTedge>& V) {
128     for(unsigned int i = 0; i < V.size(); i++) {
129         fa[V[i].from] = V[i].from;
130         fa[V[i].to] = V[i].to;
131         g[V[i].from] = NULL;
132         g[V[i].to] = NULL;
133     }
134     edgeflag = 1;
135     return;
136 }
137 
138 inline bool cmp(MSTedge& a, MSTedge& b) {return a.val < b.val;}
139 
140 inline ll kruskal(std::vector <MSTedge>& V) {
141     srand(time(0));
142     ll ans = 0;
143     std::sort(V.begin(), V.end(), cmp);
144     for(unsigned int i = 0; i < V.size(); i++) {
145         int u = V[i].from, v = V[i].to, w = V[i].val;
146         int fau = findf(u);
147         int fav = findf(v);
148         if(fav == fau) continue;
149         ans += w, ansdis += w; // ansdis 存 MST 中不会被删的边的边权
150         // 我们先将他定为 MST 边权和,再在后面减去新虚树中的边的边权
151         (rand() & 1) ? (fa[fav] = fau) : (fa[fau] = fav);
152         connect(u, v, w);
153         connect(v, u, w);
154     }
155     return ans;
156 }
157 
158 bool buildVT(int now, int f) { // 处理出所有 key 点
159     int tot = isKey[now];
160     // tot 是 自己是否是key点 与 有key点的子树 的数量和
161     for(edge* e = g[now]; e; e = e->next) {
162         int to = e->to;
163         if(to == f) continue;
164         tot += buildVT(to, now);
165     }
166     if(tot >= 2) isKey[now] = 1;
167     return !!tot;
168 }
169 
170 // 将上面处理出来的 key 连边形成虚树
171 // last 是上面的那个 key 点, max 是路径上的权值最大值
172 void processVal(int now, int f, int last, int max, std::vector <MSTedge>& V) {
173     if(isKey[now]) {
174         if(last) {
175             V.push_back(MSTedge(now, last, max));
176             ansdis -= max;
177         }
178         last = now, max = 0;
179     }
180     for(edge* e = g[now]; e; e = e->next) {
181         int to = e->to;
182         if(to == f) continue;
183         processVal(to, now, last, std::max(max, e->val), V);
184     }
185     return;
186 }
187 
188 #define nextpos(x) ((x == m) ? (1) : (x + 1))
189 
190 // type: 1(pre), 2(suf), 3(process answer)
191 // 将 a 与 b 与 x 那一列向右的一排边合并
192 inline MST merge(MST& a, MST& b, int x, int type) {
193     static std::vector <MSTedge> V;
194     // 存储 a虚树 与 b虚树 与 x 向右的那一排边
195     MST ans; ansdis = 0; V.clear();
196     for(int i = 0; i < a.size(); i++) V.push_back(a.e[i]);
197     for(int i = 0; i < b.size(); i++) V.push_back(b.e[i]);
198     for(int i = 1; i <= n; i++)
199         V.push_back(MSTedge(id[i][x], id[i][nextpos(x)], right[i][x]));
200     
201     clear(V);
202     ll mstval = kruskal(V);
203 
204     if(type == 3) { // 前后缀合并那就直接做完了
205         ans.sum = a.sum + b.sum + mstval;
206         return ans;
207     } else if(type == 1) { // 后面两种情况就开始求 虚树
208         for(int i = 1; i <= n; i++) {
209             isKey[id[i][1]] = 1;
210             isKey[id[i][x + 1]] = 1;
211         }
212     } else {
213         for(int i = 1; i <= n; i++) {
214             isKey[id[i][m]] = 1;
215             isKey[id[i][x]] = 1;
216         }
217     }
218 
219     buildVT(V[0].from, 0); // 处理虚树
220     V.clear();
221     processVal(V[0].from, 0, 0, 0, V); // 处理出虚树
222     for(unsigned int i = 0; i < V.size(); i++)
223         isKey[V[i].from] = isKey[V[i].to] = 0;
224     ans.e = V;
225     ans.sum = a.sum + b.sum + ansdis;
226     return ans;
227 }
228 
229 inline void getPre() {
230     pre[1] = MST(1);
231     for(int i = 2; i <= m; i++) {
232         pre[i] = MST(i);
233         pre[i] = merge(pre[i - 1], pre[i], i - 1, 1);
234     }
235     return;
236 }
237 
238 inline void getSuf() {
239     suf[m] = MST(m);
240     for(int i = m - 1; i; i--) {
241         suf[i] = MST(i);
242         suf[i] = merge(suf[i + 1], suf[i], i, 2);
243     }
244     return;
245 }
246 
247 inline void setting(){
248 #ifndef ONLINE_JUDGE
249     freopen("_test.in", "r", stdin);
250     freopen("_test.out", "w", stdout);
251 #endif
252     return;
253 }
254 
255 inline int main(){
256     setting();
257     gen::gen();
258     getPre(); getSuf();
259     int T; read(T);
260     while(T--) {
261         int l, r; read(l, r);
262         printf("%lld\n", merge(suf[r + 1], pre[l - 1], m, 3).sum);
263     }
264     return 0;
265 }
266 
267 }
268 
269 signed main(){ return chiaro::main();}
View Code
posted @ 2020-04-20 14:04  Chiaro  阅读(327)  评论(0)    收藏  举报