GYM 101142 G.Gangsters in Central City(树链剖分LCA)

15 篇文章 0 订阅
12 篇文章 0 订阅

Description

给出一棵 n 个节点的树,根节点为1表示水源,叶子节点表示住户,水沿着树边流向住户,两种操作

1. + v ,表示 v 住户有小偷进入,保证之前v住户没有小偷

2.  v 表示 v 住户里的小偷离开,保证之前v住户有小偷

每次操作结束后,问至少需要断几条树边可以使得所有有小偷的住户没有水喝,在断边数最少的前提下,使得没有进小偷但被断水的住户数最少

Input

第一行两个整数 n,q 分别表示点数和操作数,之后输入 n1 个整数分别表示 2,3,...,n 节点的父亲节点,最后 q 行每行一个操作(2n105,1q105)

Output

对于每个操作输出两个整数分别表示操作结束后使得所有有小偷的住户没有水和需要断的最少树边数,及最少的被断水但是没有进小偷的住户数

Sample Input

7 6
1 2 1 3 3 3
+ 4
+ 5
+ 6
+ 7
6
5

Sample Output

1 0
2 0
2 1
2 0
2 1
2 0

Solution

考虑所有 1 的儿子节点s1,s2,...,sk,显然断 k 条边就可以让所有住户没水喝,即每颗子树至多断一条边,而为使被影响的住户数最少,断边位置需要考虑,对于一棵子树,找到其中进小偷的住户位置的LCA,断掉 LCA 和其父亲节点的边即达到目的,而多个点的 LCA 只需找到这些点中 dfs 序最小的和最大的点的 LCA 即可,故用 set 维护每颗子树中进小偷的住户(具体存的是这些住户的 dfs 序),开个数组维护每颗子树中进小偷住户的数量,便于判断该棵子树是否需要断边以及求出被影响的住户数,求 LCA 树链剖分即可

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define maxn 100005
int n,q;//fa[i][j]表示i的2^j级祖先 
int fa[maxn][18];
int belong[maxn];//belong[i]表示i属于哪棵子树 
int Size[maxn];//Size[i]表示以i为根的子树中叶子节点个数 
int Num[maxn];//Num[i]表示以i为根的子树中被标记的点的个数 
int Deep[maxn];//Deep[i]表示i节点的深度 
int Index; 
int dfn[maxn];//dfn[i]表示i节点的dfs序 
int ID[maxn];//ID[i]表示以i为dfs序的节点编号 
vector<int>g[maxn];
set<int>s[maxn];
void dfs(int u,int f,int dep)
{
    Deep[u]=dep;
    belong[u]=f;
    dfn[u]=++Index;
    ID[Index]=u;
    if(!g[u].size())Size[u]=1;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        dfs(v,f,dep+1);
        Size[u]+=Size[v];
    }
}
int lca(int a,int b)
{
    int i,j;
    if(Deep[a]<Deep[b])swap(a,b);
    for(i=0;(1<<i)<=Deep[a];i++);
    i--;
    for(j=i;j>=0;j--)
        if(Deep[a]-(1<<j)>=Deep[b])
            a=fa[a][j];
    if(a==b) return a;
    for(j=i;j>=0;j--)
    {
        if(fa[a][j]&&fa[a][j]!=fa[b][j])
        {
            a=fa[a][j];
            b=fa[b][j];
        }
    }
    return fa[a][0];
}
int main()
{
    freopen("gangsters.in","r",stdin);
    freopen("gangsters.out","w",stdout); 
    while(~scanf("%d%d",&n,&q))
    {
        for(int i=1;i<=n;i++)g[i].clear(),s[i].clear();
        memset(fa,0,sizeof(fa));
        memset(Size,0,sizeof(Size));
        memset(Num,0,sizeof(Num));
        for(int i=2;i<=n;i++)
        {
            int j;
            scanf("%d",&j);
            fa[i][0]=j,g[j].push_back(i);
        }
        Deep[1]=0;
        Index=0;
        for(int i=0;i<g[1].size();i++)
        {
            dfs(g[1][i],g[1][i],1);
            Size[1]+=Size[g[1][i]];
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<18;j++)
                fa[i][j]=fa[fa[i][j-1]][j-1];
        int ans1=0,ans2=0;
        while(q--)
        {
            char op[3];
            int v;
            scanf("%s%d",op,&v);
            int root=belong[v];
            if(op[0]=='+')
            {
                if(!Num[root]) 
                {
                    ans1++;
                    Num[root]++;
                    s[root].insert(dfn[v]);
                }
                else
                {
                    ans2-=Size[lca(ID[*s[root].begin()],ID[*--s[root].end()])]-Num[root];
                    Num[root]++;
                    s[root].insert(dfn[v]);
                    ans2+=Size[lca(ID[*s[root].begin()],ID[*--s[root].end()])]-Num[root];
                }
            }
            else 
            {
                if(Num[root]==1)
                {
                    ans1--;
                    Num[root]=0;
                    s[root].erase(dfn[v]);
                }
                else
                {
                    ans2-=Size[lca(ID[*s[root].begin()],ID[*--s[root].end()])]-Num[root];
                    Num[root]--;
                    s[root].erase(dfn[v]);
                    ans2+=Size[lca(ID[*s[root].begin()],ID[*--s[root].end()])]-Num[root];
                }
            }
            printf("%d %d\n",ans1,ans2);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值