题解 P4175 【[CTSC2008]网络管理】

Juan_feng

2019-08-19 07:33:15

Solution

一万年了。 小蒟蒻Jf终于更博啦>_< 感觉自己已经彻底变成一个鸽子了呢qwq。 ———————————————以上是闲话————————————————————————— ## 这里提供一个树分块的题解。 其时间复杂度为(m+n) sqrt (n) 空间复杂度为n sqrt (n) **前置芝士**: O1lca(这个挺基础的吧) 值域分块(可以参考[小蒟蒻之前的blog](https://www.luogu.org/blog/Juan-feng/solution-p3834)) 那么废话不多说, 直接开始讲实现方法。 在树上选出T个关键点(选取的方法和性质后面会说), 在把这些关键点之间的LCA也设成关键点,然后将值域分块,处理出这些关键点到根的路径中的一些信息: > cnt1(i, j)表示第i个关键点到根的路径中, 权值在第j个值域块中的点的数量 > cnt2(i, j) 表示第i个关键点到根的路径中, 权值为j的点的数量 显然上面的两个数组可以在T * n 的时间内处理完成。 然后关于这T个点的选取, 我们想让其拥有这样的性质: 从树上的任何一个点出发向根跳最多跳T*(常数)次就可以跳到一个关键点。 显然可以通过贪心来选取这T个点, 然后小Jf比较懒, 就随机了T个点, 据某毒瘤说复杂度也是正确的qwq。(贪心的具体方法本文不进行具体介绍, 可以参考2015年邹逍遥的国家集训队论文) 有了上面的性质, 问题就简单了。 我们先对树进行染色。 将树上向根上跳**第一个**遇到的**关键点相同**的**点** 染上相同的颜色。 那么对于询问x,y,k。 如果xy的颜色**相同**, 那么显然可以在O(T)的实现内暴力进行查询(记录下路径上经过的点用值域分块进行查询)。 对于颜色**不同**的x,y, 我们可以进行如下的讨论: ![pic1.jpg](https://i.loli.net/2019/08/19/ELoWRh9za7f6TDN.png) 情况1: 如上图,记x的颜色为xx, y的颜色为yy, xx和yy并非祖先-后代关系。 这种情况是最简单的, 直接处理一下散块(x到xx, y到yy)的信息(蓝色) 然后xx到yy的信息(红色) 可以通过**xx到根的信息** + **yy到根的信息** - **2*lca(xx, yy)到根的信息** 来得到。 然后值域分块来求k大就可以了。 ![pic2.png](https://i.loli.net/2019/08/19/YJyUZRCelbtjOrq.png) 情况2:如上图,记x的颜色为xx, y的颜色为yy, xx和yy是祖先=后代关系, **且**lca(x,y) 是xx,yy中的一个点的祖先, 同时是其中另外一个点的后代。 这种情况下, 如果我们和之前一样处理散块(x到xx,y到yy)的信息(蓝色)和xx到yy的信息(红色)的话, 就会发现多处理了图上<红蓝色>的部分, 而且多处理了两次。 这个时候我们只需要在处理散块信息的时候特殊处理一下就行了。 恩。 那么查询就讲完了qwq 其实修改也很简单, 直接判断一下所有关键点到根的路径中是否经过修改的点, 如果经过的话就在处理的信息中单次O1进行修改就可以了>_< (注意这里需要O1查询的lca, 不然复杂度带log)效率为O(T) 当T取sqrt(n)时, 算法的时间复杂度为(n+m)sqrt(n) 空间复杂度为n sqrt(n) 貌似就没有什么啦qwq 有什么问题的话欢迎来私信小蒟蒻啊qwqwq **那么代码如下:** ``` #include <iostream> #include <cstring> #include <cmath> #include <cstdio> #include <algorithm> #include <ctime> #define maxn 400010 #define maxs 330 #define re register #define FOR(i, l, r) for(re int i = l; i <= r; ++i) #define DFR(i, l, r) for(re int i = l; i >= r; --i) using namespace std; int n, m, c, r, t, x, y, k, tot; int sq1, sq2, num, cnt, col, theone, fh, lastans; int a[maxn], b[maxn], depth[maxn], cnt1[maxs*2][maxs], cnt2[maxs*2][maxn]; int qwq[maxn], san1[maxn], san2[maxn], dis[maxn], head[maxn], numb[maxn]; int fa[maxn], near[maxn], z[maxn], z1[maxn], q[maxn][3]; int f[maxn][20], lg[maxn], id[maxn], vis[maxn], Depth[maxn]; struct hz { int next; int to; }h[maxn*2]; inline int read(){ int x=0;int f=1;char c=getchar(); while(c<'0'||c>'9'){ if(c==-1) return 0; if(c=='-')f=-1; c=getchar(); } while(c<='9'&&c>='0'){ x=(x<<1)+(x<<3)+(c^'0'); c=getchar(); } return x*f; } inline void add(int from, int to) { h[++num].next = head[from]; h[num].to = to; head[from] = num; } void dfs(int x, int ff, int dep) { fa[x] = ff; id[x] = ++tot; depth[x] = depth[ff]+1; vis[tot] = x; Depth[tot] = dep; for(re int i = head[x]; i != 0; i = h[i].next) { if(h[i].to == ff) continue; dfs(h[i].to, x, dep+1); vis[++tot] = x; Depth[tot] = dep; } } void RMQ() { FOR(i, 1, tot) lg[i] = lg[i-1]+(1 << lg[i-1] == i); FOR(i, 1, tot) f[i][0] = i; for(int i = 1; (1<<i) <= tot; i++) { for(int j = 1; j+(1 << i)-1 <= tot; j++) { int A = f[j][i-1]; int B = f[j+(1 << (i-1))][i-1]; if(Depth[A] <= Depth[B]) f[j][i] = A; else f[j][i] = B; } } } int lca(int x, int y) { int r = id[x]; int l = id[y]; if(r < l) swap(r, l); int k = lg[r-l+1]-1; int A = f[l][k]; int B = f[r-(1<<k)+1][k]; if(Depth[A] <= Depth[B]) return vis[A]; else return vis[B]; } void dfs3(int x, int color) { //染色 if(numb[x] != 0) color = numb[x]; near[x] = color; //属于什么管辖范围 for(re int i = head[x]; i != 0; i = h[i].next) { if(h[i].to == fa[x]) continue; dfs3(h[i].to, color); } } inline void deal(int x, int times) { while(x != 0) { ++cnt1[times][b[a[x]]]; ++cnt2[times][a[x]]; x = fa[x]; } } int query(int x, int y, int k) { //求k大 //xy在一个颜色块里, 则直接暴力跳到lca //xy不在一个颜色块里, 且一部分路径重复计算-> 经过lca后转化加减法 if(near[x] == near[y]) { int lc = lca(x, y), xx = x, yy = y, anss = 0; while(x != lc) ++san1[b[a[x]]], ++san2[a[x]], x = fa[x]; while(y != fa[lc]) ++san1[b[a[y]]], ++san2[a[y]], y = fa[y]; int tot = 0, now = 1; while(tot+san1[now] < k) tot += san1[now], ++now; FOR(j, (now-1)*sq1+1, now*sq1) if((tot += san2[j]) >= k) { anss = z[j]; break; } while(xx != lc) --san1[b[a[xx]]], --san2[a[xx]], xx = fa[xx]; while(yy != fa[lc]) --san1[b[a[yy]]], --san2[a[yy]], yy = fa[yy]; return anss; } int lc = lca(x, y), LC = lca(z1[near[x]], z1[near[y]]), xx = x, yy = y; theone = -1, fh = 1; if(lca(z1[near[x]], z1[near[y]]) == LC && (lca(lc, z1[near[x]]) == lc || lca(lc, z1[near[y]])) && lc != LC) { //需要转化加减答案 theone = lc; san1[b[a[LC]]]--, san2[a[LC]]--; } while(near[fa[xx]] == near[x]) { if(xx == theone) { fh *= -1; } else { san1[b[a[xx]]] += fh, san2[a[xx]] += fh; } if(xx == 0) exit(0); xx = fa[xx]; } fh = 1; while(near[fa[yy]] == near[y]) { if(yy == theone) { fh *= -1; yy = fa[yy]; continue; } san1[b[a[yy]]] += fh, san2[a[yy]] += fh, yy = fa[yy]; } ++san1[b[a[LC]]]; ++san2[a[LC]]; int tot = 0, now = 1, anss = 0; while(tot+san1[now]+cnt1[near[x]][now]+cnt1[near[y]][now]-2*cnt1[numb[LC]][now] < k) tot += san1[now]+cnt1[near[x]][now]+cnt1[near[y]][now]-2*cnt1[numb[LC]][now], ++now; FOR(j, (now-1)*sq1+1, now*sq1) if((tot += san2[j]+cnt2[near[x]][j]+cnt2[near[y]][j]-2*cnt2[numb[LC]][j]) >= k) { anss = z[j]; break; } fh = -1, xx = x, yy = y; while(near[fa[xx]] == near[x]) { if(xx == theone) { fh *= -1; xx = fa[xx]; continue; } san1[b[a[xx]]] += fh, san2[a[xx]] += fh, xx = fa[xx]; } fh = -1; while(near[fa[yy]] == near[y]) { if(yy == theone) { fh *= -1; yy = fa[yy]; continue; } san1[b[a[yy]]] += fh, san2[a[yy]] += fh, yy = fa[yy]; } --san1[b[a[LC]]]; --san2[a[LC]]; if(lca(z1[near[x]], z1[near[y]]) == LC && (lca(lc, z1[near[x]]) == lc || lca(lc, z1[near[y]])) && lc != LC) { //需要转化加减答案 san1[b[a[LC]]]++, san2[a[LC]]++; } return anss; } signed main() { srand(19260817); srand(rand()); srand(rand()); n = read(), m = read(); FOR(i, 1, n) { a[i] = read(), z[++z[0]] = a[i]; qwq[i] = i; } FOR(i, 1, n-1) { x = read(), y = read(); add(x, y); add(y, x); } dfs(1, 0, 1); RMQ(); FOR(i, 1, m) { q[i][0] = read(), q[i][1] = read(), q[i][2] = read(); if(q[i][0] == 0) z[++z[0]] = q[i][2]; } sort(z+1, z+z[0]+1); z[0] = unique(z+1, z+z[0]+1)-z-1; FOR(i, 1, n) a[i] = lower_bound(z+1, z+z[0]+1, a[i])-z; sq1 = sqrt(z[0]); //值域分块 FOR(i, 1, z[0]) b[i] = (i-1)/sq1+1; random_shuffle(qwq+1, qwq+n+1); //选取前sq个点 sq2 = sqrt(n); //关键点的数量 FOR(i, 1, sq2) z1[++z1[0]] = qwq[i]; FOR(i, 1, sq2) FOR(j, i+1, sq2) z1[++z1[0]] = lca(qwq[i], qwq[j]); sort(z1+1, z1+z1[0]+1); //关键点排序并去重 z1[0] = unique(z1+1, z1+z1[0]+1)-z1-1; FOR(i, 1, z1[0]) numb[z1[i]] = i; //关键点的新序号 dfs3(1, -2); //染色 FOR(i, 1, z1[0]) //枚举所有新关键点(sq个) 进行处理; deal(z1[i], i); FOR(i, 1, m) { k = q[i][0], x = q[i][1], y = q[i][2]; int sizz = depth[x]+depth[y]-2*depth[lca(x, y)]+1; if(k == 0) { y = lower_bound(z+1, z+z[0]+1, q[i][2])-z; FOR(j, 1, z1[0]) { if(lca(z1[j], x) != x) continue; --cnt1[j][b[a[x]]]; --cnt2[j][a[x]]; ++cnt1[j][b[y]]; ++cnt2[j][y]; } a[x] = y; continue; } if(sizz < k) { puts("invalid request!"); continue; } printf("%d\n", query(x, y, sizz-k+1)); } } ```