题目描述
Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达。
游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。
我们将以如下形式定义每一种操作:
- C(hange) i 改变第i个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
- G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。
输入输出格式
输入格式:
第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。
接下来N-1行每行两个整数a, b,表示房间a与房间b之间有一条走廊相连。
接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如上文所示。
输出格式:
对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关着灯的,输出0;若所有房间的灯都开着,输出-1。
输入输出样例
说明
对于20%的数据, N ≤50, M ≤100;
对于60%的数据, N ≤3000, M ≤10000; 对于100%的数据, N ≤100000, M ≤500000。
可以说是点分树的模板题目了。首先对于只有一个询问的情况我们可以暴力bfs解决,那么既然要带修改的话自然考虑点分树:把点分治每一层建立成树形结构。对每一块分治维护:1、当前分治的子树下,以连向上一层的点为根向下最深关灯房间的深度。2、当前重心下,把每一个子树的最深关灯房间拿出来,其中深度值的最大值和次大值。(一个是不能在同一个子树,因为必须经过重心。另外如果当前重心为关灯的房间的话,那么0也是一个合法的值)。并且要支持修改。那么我们就开两个map,分别对重心维护以上的两个值。每有一个修改就沿着点分树跳到根,一边跳一边更新两个信息,并且更改当前重心统计的答案。最后再用一个map来维护所有重心统计答案的最大值就好了。
但是map常数太大了,有没有有没一点的写法呢?
答案是有的,我们可以写一个可删除的堆!具体做法是对于一个可删除堆,我们用两个堆维护,一个记录删除的键值,一个记录插入的键值。每有一个插入我们把它加到插入堆,每有一个删除我们把它加到删除堆。这样之后,只需要再top()和pop()时,把两个堆堆顶相同的部分先全部弹掉再操作就好了。因为删除最多删除插入的那么多个,所以总复杂度还是O(n log n)的,而且常数和map相比非常小。
// luogu-judger-enable-o2 #include<cstdio> #include<algorithm> #include<iostream> #include<queue> using namespace std; const int maxn = 1e5 + 5; const int inf = 0x3f3f3f3f; struct edge { int v, next; }e[maxn << 1]; int head[maxn], cnt, tot, ans; void adde(const int &u, const int &v) { e[++cnt] = (edge) {v, head[u]}; head[u] = cnt; } struct Heap { priority_queue<int > M, D; void push(int x) {M.push(x);} void del(int x) {D.push(x);} void pop() { while(!M.empty() && !D.empty() && M.top() == D.top()) M.pop(), D.pop(); M.pop(); } int top() { while(!M.empty() && !D.empty() && M.top() == D.top()) M.pop(), D.pop(); return M.empty() ? -1 : M.top(); } int Qms() { int mx1 = top(), mx2, ans; if(!(~mx1)) return 0; pop(), mx2 = top(); if(!(~mx2)) return push(mx1), 0; return push(mx1), mx1 + mx2; } } Ans[maxn], Nd[maxn], All; int size[maxn], mx[maxn], G, vis[maxn]; void FG(int u, int f, int & G, int tot) { size[u] = 1, mx[u] = 0; for(register int i = head[u]; i; i = e[i].next) { int v = e[i].v; if(vis[v] || v == f) continue; FG(v, u, G, tot), size[u] += size[v]; mx[u] = max(mx[u], size[v]); } mx[u] = max(mx[u], tot - size[u]); if(mx[u] < mx[G]) G = u; } int LG[maxn], fa[maxn], lyr[maxn], Dp[maxn][22], Tp[maxn], Ap[maxn]; void GetInit(int u, int f, int G, int L, int w) { size[u] = 1; Dp[u][L] = w, Nd[G].push(w); for(register int i = head[u]; i; i = e[i].next) { int v = e[i].v; if(vis[v] || v == f) continue; GetInit(v, u, G, L, w + 1); size[u] += size[v]; } } void solve(int u, int rt, int L) { int v, tmp; GetInit(rt, 0, u, L, 1), vis[u] = 1, lyr[u] = L; for(register int i = head[u]; i; i = e[i].next) { v = e[i].v; if(vis[v]) continue; G = 0, FG(v, 0, G, size[v]), tmp = G; fa[G] = u, solve(G, v, L + 1); Tp[tmp] = Nd[tmp].top(); Ans[u].push(Tp[tmp]); } Ans[u].push(0); All.push(Ap[u] = Ans[u].Qms()); } void CG(int u) { if(!LG[u]) ++tot, LG[u] = 1, Ans[u].del(0); else --tot, LG[u] = 0, Ans[u].push(0); int tmp, p = u, as = Ans[u].Qms(), fp = fa[p]; if(Ap[u] != as) All.push(as), All.del(Ap[u]), Ap[u] = as; while(fp) { if(!LG[u]) Nd[p].push(Dp[u][lyr[p]]); else Nd[p].del(Dp[u][lyr[p]]); tmp = Nd[p].top(); if(Tp[p] != tmp) { if(~Tp[p]) {Ans[fp].del(Tp[p]);} if(~tmp) {Ans[fp].push(tmp);} Tp[p] = tmp; } as = Ans[fp].Qms(); if(Ap[fp] != as) All.push(as), All.del(Ap[fp]), Ap[fp] = as; p = fa[p], fp = fa[p]; } } char s[3]; int n, m, u, v, root; int main() { mx[0] = inf; scanf("%d", &n); for(register int i = 1; i < n; ++i) scanf("%d%d", &u, &v), adde(u, v), adde(v, u); G = 0, FG(1, 0, G, n), root = G; solve(G, 1, 0); scanf("%d", &m); for(register int i = 1; i <= m; ++i) { scanf("%s", s); if(s[0] == 'G') printf("%d\n", All.top()); else scanf("%d", &u), CG(u); } return 0; }
没有帐号? 立即注册