题目大意
现有一颗有根树,叶子节点可被染成黑色(最开始都是白色),也可以被染回白色,现在问给出每次染色信息后(即带修改),最少需要切断几条边保证黑色叶子不在树上,且在此条件下最少有多少白色叶子不在树上。
解题思路
大体算法:DFS序+倍增
很容易发现,被切断的边数最多只需要与根节点相连的边数,因为根节点连出去的每颗子树最多只需要一条边就可以切断,所以每颗子树都是独立的。
这样问题就转化为了找出每课子树中被染黑的叶子节点的最近公共祖先,多个点的LCA我发现可以用DFS序+倍增在O(logN)找出(没百度到相关解法,自己YY加上hzh提示想出来的)
具体实现:
从根出发dfs,dfs的时候需要维护四个东西:1.时间戳,dfn[]记录访问时间,low[]记录子树中最晚访问的点的dfn[];2.f[][],倍增方法记录祖先;3.siz[]统计子树中叶子节点的个数;4.所属子树编号
然后对每颗子树建立一个大根堆和小根堆,维护一颗子树中黑色叶子dfn的最大值maxx和最小值minn,因为还要带删除,如果用用优先队列还需要一个辅助数组。然后从一个黑色叶子节点出发,用倍增找出最近公共祖先,条件是(dfn[fa] <= minn && low[fa] >= maxx),找到LCA后,再利用利用siz数组,很容易得出答案
~~~代码比较丑
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 100010;
const int lg = 20;
int f[maxn][lg];
int n,q;
int pre_ans[maxn];
int pre_cnt[maxn];
int siz[maxn],cnt;
int x,index = 1;
char c;
int dfn[maxn],low[maxn],p[maxn];
bool co[maxn];
struct edge
{
int v,next;
}e[maxn << 1];
int h[maxn],num;
int pos_cnt = 0;
struct node_max
{
int pos,x;
node_max(int _pos,int _x)
{
pos = _pos;
x = _x;
}
friend bool operator< (node_max a,node_max b)
{
return a.x < b.x;
}
};
struct node_min
{
int pos,x;
node_min(int _pos,int _x)
{
pos = _pos;
x = _x;
}
friend bool operator< (node_min a,node_min b)
{
return a.x > b.x;
}
};
priority_queue<node_max>qmax[maxn];
priority_queue<node_min>qmin[maxn];
void build_edge(int u,int v)
{
num++;
e[num].v = v;
e[num].next = h[u];
h[u] = num;
}
void dfs(int x,int pos,int fa)
{
dfn[x] = ++index;
p[x] = pos;
f[x][0] = fa;
for(int i = 1; i < lg; i++)
f[x][i] = f[f[x][i-1]][i-1];
for(int i = h[x]; i; i = e[i].next)
{
dfs(e[i].v,pos,x);
siz[x] += siz[e[i].v];
}
low[x] = index;
if(low[x] == dfn[x])
siz[x] = 1;
}
bool inqueue_max[maxn];
bool inqueue_min[maxn];
int getRoot(int x)
{
if(pre_cnt[x] == 0)
return 0;
while(!qmax[x].empty())
{
if(!co[qmax[x].top().x])
inqueue_max[qmax[x].top().x] = false,qmax[x].pop();
else
break;
}
while(!qmin[x].empty())
{
if(!co[qmin[x].top().x])
inqueue_min[qmin[x].top().x] = false,qmin[x].pop();
else
break;
}
int maxx,minn,u;
maxx = qmax[x].top().x;
minn = qmin[x].top().x;
u = qmax[x].top().pos;
if(pre_cnt[x] == 1)
return u;
for(int i = lg - 1; i >= 0; i--)
while(dfn[f[u][i]] > minn || low[f[u][i]] < maxx)
u = f[u][i];
return f[u][0];
}
int main()
{
freopen("gangsters.in","r",stdin);
freopen("gangsters.out","w",stdout);
scanf("%d%d",&n,&q);
for(int i = 2; i <= n; i++)
{
scanf("%d",&x);
build_edge(x,i);
}
dfn[1] = 1, dfn[0] = 0;
low[1] = n, low[0] = 9999999;
for(int i = h[1]; i; i = e[i].next)
{
pos_cnt++;
dfs(e[i].v,pos_cnt,1);
siz[1] += siz[e[i].v];
}
int ans_cnt = 0;
int ans_edge = 0;
int now_ans;
int root;
while(q--)
{
scanf(" %c%d",&c,&x);
if(c == '+')
{
pre_cnt[p[x]]++;
if(pre_cnt[p[x]] == 1)
ans_edge++;
co[dfn[x]] = true;
if(!inqueue_max[dfn[x]])
qmax[p[x]].push(node_max(x,dfn[x]));
else
inqueue_max[dfn[x]] = false;
if(!inqueue_min[dfn[x]])
qmin[p[x]].push(node_min(x,dfn[x]));
else
inqueue_min[dfn[x]] = false;
}
else
{
co[dfn[x]] = false;
pre_cnt[p[x]]--;
if(pre_cnt[p[x]] == 0)
ans_edge--;
inqueue_max[dfn[x]] = true;
inqueue_min[dfn[x]] = true;
}
root = getRoot(p[x]);
now_ans = siz[root] - pre_cnt[p[x]];
ans_cnt += now_ans - pre_ans[p[x]];
pre_ans[p[x]] = now_ans;
printf("%d %d\n",ans_edge,ans_cnt);
}
return 0;
}