2016-2017 ACM-ICPC, NEERC, Northern Subregional Contest G - Gangsters in Central City

题目大意

现有一颗有根树,叶子节点可被染成黑色(最开始都是白色),也可以被染回白色,现在问给出每次染色信息后(即带修改),最少需要切断几条边保证黑色叶子不在树上,且在此条件下最少有多少白色叶子不在树上。

解题思路

大体算法: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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值