hihocoder 1145 幻想乡的日常

题目链接

时间限制: 20000ms
单点时限: 1000ms
内存限制: 256MB

描述

幻想乡一共有n处居所,编号从1到n。这些居所被n-1条边连起来,形成了一个树形的结构。

每处居所都居住着一个小精灵。每天小精灵们都会选出一个区间[l,r],居所编号在这个区间内的小精灵一起来完成一项任务。

特别的,居所相邻的(有边相连的)两个小精灵会自发的组成一队,并且如果a和b相邻b和c相邻,那么a和c也在同一队里面。每天的任务完成之后,队伍就会解散;第二天再根据新的区间组成新的队伍。

给出每天小精灵们选出的区间,你知道每天组成的队伍数量吗?

输入

第一行两个数n和Q(1 <= n, Q <= 100000),表示居所的数目和组队的天数。

接下来n-1行,每行两个数a和b,表示居所a和b之间有一条边。

接下来Q行,每行两个数l和r,满足1<=l<=r<=n,为该天小精灵选出的区间。

输出

输出Q行,每行一个整数表示该天队伍的数量。

样例输入
3 1
1 2
2 3
1 3
样例输出
1

题解:由于图是树,所以这题有个很重要的性质:对于一个点区间 [L,R] 的联通块的数目,等于这个区间的点数,减去这个区间中的点与点之间的边数。

对于边(u,v),u<v,看成一个子区间 [u,v]。那么问题就转换为求 [L,R] 区间内包含多少个子区间 。 这是一个经典的问题。离线处理询问,按R值从小到大排序。从1扫到n,碰到一个子区间的右值v则在左值u处加1。当我们碰到询问的右值r的时候,这是所有访问过的子区间的右值都<=R,由于我们在已访问过的子区间的左值处加1,这时区间[L,R]的和就是在该区间中的子区间的数目。

代码如下:

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
#define mod 1000000007
#define nn 110000
typedef long long LL;
using namespace std;
struct node
{
    int en,next;
}E[nn*2];
int n,q;
int p[nn],num;
void init()
{
    memset(p,-1,sizeof(p));
    num=0;
}
void add(int st,int en)
{
    E[num].en=en;
    E[num].next=p[st];
    p[st]=num++;
}
struct ask
{
    int id;
    int l,r;
}a[nn];
int ans[nn];
bool cmp(ask x,ask y)
{
    return x.r<y.r;
}
int tree[nn];
inline int lowbit(int x)
{
    return (x&-x);
}
void jia(int id,int x)
{
    for(int i=id;i<=n;i+=lowbit(i))
    {
        tree[i]+=x;
    }
}
int sum(int id)
{
    int re=0;
    while(id)
    {
        re+=tree[id];
        id-=lowbit(id);
    }
    return re;
}
int main()
{
    int i,u,v;
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        init();
        for(i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
            add(v,u);
        }
        for(i=1;i<=q;i++)
        {
            scanf("%d%d",&a[i].l,&a[i].r);
            a[i].id=i;
        }
        sort(a+1,a+q+1,cmp);
        memset(tree,0,sizeof(tree));
        int ix=1,w;
        for(i=1;i<=n;i++)
        {
            for(int j=p[i];j+1;j=E[j].next)
            {
                w=E[j].en;
                if(w<i)
                {
                    jia(w,1);
                }
            }
            while(i==a[ix].r&&ix<=q)
            {
                ans[a[ix].id]=a[ix].r-a[ix].l+1-(sum(a[ix].r)-sum(a[ix].l-1));
                ix++;
            }
        }
        for(i=1;i<=q;i++)
        {
            printf("%d\n",ans[i]);
        }
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值