SPOJ Query on a tree again! 树链剖分+树状数组

https://www.spoj.com/problems/QTREE3/

You are given a tree (an acyclic undirected connected graph) with N nodes. The tree nodes are numbered from 1 to N. In the start, the color of any node in the tree is white.

We will ask you to perfrom some instructions of the following form:

  • 0 i : change the color of the i-th node (from white to black, or from black to white);
    or
  • 1 v : ask for the id of the first black node on the path from node 1 to node v. if it doesn't exist, you may return -1 as its result.

Input

In the first line there are two integers N and Q.

In the next N-1 lines describe the edges in the tree: a line with two integers a b denotes an edge between a and b.

The next Q lines contain instructions "0 i" or "1 v" (1 ≤ i, v ≤ N).

Output

For each "1 v" operation, write one integer representing its result.

Example

Input:
9 8
1 2
1 3
2 4
2 9
5 9
7 9
8 9
6 8
1 3
0 8
1 6
1 7
0 2
1 9
0 2
1 9 

Output:
-1
8
-1
2
-1

Constraints & Limits

There are 12 real input files.

For 1/3 of the test cases, N=5000, Q=400000.

For 1/3 of the test cases, N=10000, Q=300000.

For 1/3 of the test cases, N=100000, Q=100000.

题目大意:给一棵树,每个节点只能是黑色或白色,初始全为白色。有两种操作,第一种把第i个节点的颜色翻转,第二种输出从节点1到节点i的路径上的第一个黑色节点,若无输出-1。

思路:树链剖分,树状数组维护区间和。令0代表白色,1代表黑色,操作二即寻找从节点1到节点i的路径上第一个值为1的节点。那么就记录下从节点1到节点i的各个区间,从前向后遍历,若当前区间的元素之和大于1则在当前区间内二分找第一个节点,若遍历结束仍无满足题意的区间则输出-1。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define pr pair<int,int>
#define mp make_pair
using namespace std;

const int maxn=1e5+5;

int tree[maxn];
int siz[maxn];//子树大小
int son[maxn];//重儿子
int fa[maxn];//父节点
int deep[maxn];//深度
int top[maxn];//所在链链顶
int pos[maxn];//dfs序编号
int a[maxn];
int Index[maxn];
vector<int> vec[maxn];//存边
vector<pr> prc;//存区间
int n,m,tot=0;

void dfs1(int u,int f)//处理出重儿子 深度 父亲 子树大小等信息
{
    siz[u]=1;
    son[u]=0;
    fa[u]=f;
    deep[u]=deep[f]+1;
    int to;
    for(int i=0;i<vec[u].size();i++)
    {
        to=vec[u][i];
        if(to!=f)
        {
            dfs1(to,u);
            siz[u]+=siz[to];
            if(siz[son[u]]<siz[to])
                son[u]=to;
        }
    }
}

void dfs2(int u,int f,int k)//处理出 链顶 dfs序 映射后的值 等信息
{
    top[u]=k;
    pos[u]=++tot;
    Index[tot]=u;
    if(son[u])
        dfs2(son[u],u,k);
    int to;
    for(int i=0;i<vec[u].size();i++)
    {
        to=vec[u][i];
        if(to!=f&&to!=son[u])
            dfs2(to,u,to);
    }
}

inline int lowbit(int x)
{
    return x&(-x);
}

void update(int i,int v)
{
    for(;i<=n;i+=lowbit(i))
        tree[i]+=v;
}

int query(int l)
{
    int ans=0;
    for(;l;l-=lowbit(l))
        ans+=tree[l];
    return ans;
}

void work(int u,int v)//计算出u到v的路径 映射后的各段区间
{
    while(!prc.empty())
        prc.pop_back();
    while(top[u]!=top[v])
    {
        if(deep[top[u]]<deep[top[v]])
            swap(u,v);
        prc.push_back(mp(pos[top[u]],pos[u]));
        u=fa[top[u]];
    }
    if(deep[u]>deep[v])
        swap(u,v);
    prc.push_back(mp(pos[u],pos[v]));
}

inline void prework()
{
    scanf("%d%d",&n,&m);
    int u,v;
    for(int i=1;i<n;i++)
    {
        scanf("%d %d",&u,&v);
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,0,1);
}

inline void mainwork()
{
    int u,v;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&u,&v);
        if(u==0)
        {
            if(a[v]==0)
            {
                a[v]=1;
                update(pos[v],1);
            }
            else
            {
                a[v]=0;
                update(pos[v],-1);
            }
        }
        else
        {
            work(1,v);
            int l=0,r=0,tmp,mid;
            for(int i=prc.size()-1;i>=0;i--)
            {
                tmp=query(prc[i].second)-query(prc[i].first-1);
                if(tmp>=1)
                {
                    l=prc[i].first;
                    r=prc[i].second;
                    break;
                }
            }
            if(l==r&&l==0)
                printf("-1\n");
            else
            {
                int temp=l-1;
                while(l<=r)
                {
                    mid=(l+r)>>1;
                    if(query(mid)-query(temp)>=1)
                        r=mid-1;
                    else
                        l=mid+1;
                }
                printf("%d\n",Index[l]);
            }
        }
    }
}

int main()
{
    prework();
    mainwork();
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值