简略题意:一棵树,每个节点有一个局面,根是水源,边是水管。初始每个居民都有水喝。
操作有两种:
‘+ v’, v处的居民楼被强盗占领。
‘- v’, v处的强盗走了。
对于每个询问,你需要切断一些水管,使得所有强盗没水喝,没水喝的居民尽量少。
因为必须有边切才行,所以需要切除的边数最多就是与原来的根相连的边的数目。
原来的树拆成了若干个子树,每个子树的根也有一个边可以切除。
单独考虑每个子树的问题,对于这个子树内的所有强盗,只要切除根到这些强盗的lca处的任意一条边即可,不过我们需要最小化被影响的居民数,因此我们切除这些边中深度最大的lca上的边。
现在的唯一难点就是若干个点的lca怎么求,若干个点的共同lca就是这些点中dfs序的最小值和最大值点的lca.
我们可以对每个树根维护一个set,用来取这个树内的强盗点的最大dfs序和最小dfs序。
每次来一个强盗,我们找到对应的树根,插入这个点的dfs序。
每次走一个强盗,我们找到对应的树根,擦除这个点的dfs序。
然后更新答案即可。
复杂度
O(qlogn+nlogn)
.
#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double eps = 1e-8;
int dcmp(double x) { if(fabs(x)<=eps) return 0; return (x>0)?1:-1;};
typedef long long LL;
namespace solver {
const int maxn = 150000;
int n, m;
vector<int> G[maxn];
int deep[maxn], p[maxn][20], cnt[maxn], belong[maxn], sum[maxn], cut[maxn];
int L[maxn], R[maxn], id = 0;
set<int> st[maxn];
void Dfs(int u, int fa, int root) {
cnt[u] = 0;
bool isok = 1;
p[u][0] = fa;
if(root != 0)
belong[u] = root;
for(auto v:G[u]) {
if(v == fa) continue;
deep[v] = deep[u] + 1;
if(u == 1) Dfs(v, u, v);
else Dfs(v, u, root);
cnt[u] += cnt[v];
isok = 0;
}
if(isok) cnt[u] = 1, L[u] = ++id, R[id] = u;
}
int lca_up(int u, int len) {
for(int i = 19; i >= 0; i--) {
if(u != -1 && (len & (1<<i))) {
u = p[u][i];
}
}
return u;
}
int lca(int u, int v) {
if(deep[u] < deep[v]) swap(u, v);
int d = deep[u] - deep[v];
u = lca_up(u, d);
if(u == v) return u;
for(int i = 19; i >= 0; i--) {
if(p[u][i] == p[v][i]) continue;
u = p[u][i];
v = p[v][i];
}
return p[u][0];
}
void solve() {
scanf("%d%d", &n, &m);
for(int i = 2; i <= n; i++) {
int u = i, v;
scanf("%d", &v);
G[v].push_back(u);
G[u].push_back(v);
}
deep[1] = 0;
Dfs(1, -1, -1);
for(int i = 1; i < 20; i++) {
for(int j = 1; j <= n; j++) {
if(p[j][i-1] == -1) p[j][i] = -1;
else p[j][i] = p[p[j][i-1]][i-1];
}
}
int ans1 = 0, ans2 = 0;
for(int i = 1; i <= m; i++) {
char c;
int d;
scanf(" %c %d", &c, &d);
int f = belong[d];
if(c == '+') {
if(sum[f] == 0) {
ans1++;
sum[f]++;
st[f].insert(L[d]);
cut[f] = d;
} else {
int pc = cut[f];
int pv = cnt[pc] - sum[f];
sum[f]++;
st[f].insert(L[d]);
int ll = *st[f].begin(), rr = *st[f].rbegin();
ll = R[ll], rr = R[rr];
int nc = lca(ll, rr);
int nv = cnt[nc] - sum[f];
cut[f] = nc;
ans2 += nv - pv;
// cout<<nc<<" "<<cnt[nc]<<" sss "<<pv<<" "<<nv<<" "<<pc<<endl;
}
} else {
if(sum[f] == 1) {
ans1--;
sum[f]--;
st[f].erase(L[d]);
cut[f] = 0;
} else {
int pc = cut[f];
int pv = cnt[pc] - sum[f];
sum[f]--;
st[f].erase(L[d]);
int ll = *st[f].begin(), rr = *st[f].rbegin();
ll = R[ll], rr = R[rr];
int nc = lca(ll, rr);
int nv = cnt[nc] - sum[f];
cut[f] = nc;
ans2 += nv - pv;
}
}
cout<<ans1<<" "<<ans2<<endl;
}
}
}
int main() {
#ifdef file
freopen("gangsters.in", "r", stdin);
freopen("gangsters.out", "w", stdout);
#endif // file
solver::solve();
return 0;
}