牛客网暑期ACM多校训练营(第六场)I - Team Rocket —— 线段树经典变式 、多条件各个击破

其他习题--->>>博客目录
线段树超详细入门讲解

题目传送门(是付费的)

时间限制:C/C++ 4秒,其他语言8秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

There are n trains running between Kanto and Johto region. Assuming the railway is a number line, the i-th train travels from coordinate li to coordinate ri (both inclusive).

One day, m Team Rocket members invaded the railway system successfully. The i-th Team Rocket member was going to destroy the transportation hub with coordinate xi. Once a transportation hub within the driving range of a train is destroyed, the train's itinerary will be canceled immediately.

Giovanni wants to know how many train trips will be firstly canceled after each attack.

After all the attacks finished, for each train Giovanni needs to know that in which attack its itinerary was firstly canceled, or it was unaffected at all.

输入描述:

The input starts with one line containing exactly one integer T, which is the number of test cases.

For each test case, the first line contains two integers n and m, indicating the number of trains and the number of Team Rocket members.

Each of the next n lines contains 2 integers li and ri, indicating the driving range of the i-th train.

Each of the next m lines contains exactly one integer yi. , where xi is the transportation hub that Team Rocket members would destroy in the i-th attack, resi-1 is the product of the indexes of trips cancelled by the (i-1)-th attack and  means exclusive or. 

If no such trip exists, resi-1 is considered to be 0.

- 1 ≤ T ≤ 5.
- 1 ≤ n,m ≤ 2e5.
- -1e9 ≤ li ≤ ri ≤ 1e9.
- -1e9 ≤ xi ≤ 1e9.

输出描述:

For each test case, output one line "Case #x:" first, where x is the test case number (starting from 1).

Then output m lines, each line of which contains exactly one integer, indicating the number of train trips firstly canceled after the i-th attack.

Finally output one line, containing n integers, where the i-th integer is the time when the i-th train trip is firstly canceled or 0 if it is not affected.

示例1

输入

复制

1
3 4
1 3
2 6
-999 1000000000
-1000
1
5
2

输出

复制

Case #1:
0
2
1
0
2 3 2

题目大意

有2e5个线段[l,r]∈±1e9,2e5个询问,每个询问都给出一个点x,将所有经过点x的线段删掉。输出每个询问删除线段的个数,每组样例之后输出每个线段被哪次询问删掉。对于每个询问,题目强制了不能离线处理。

思路

每个线段存成一个结构体node

首先将node数组按照线段的l排序,然后以按照排序后的数组为顺序,以r为键值建立线段树维护区间最大值。

每次给出一个x,我们要找的线段满足两个条件,1.l<=x  2.r>=r。第一个条件我们可以在node数组上二分查找,找出一个区间[1,rp]满足所有的l<=x。之后的工作就是从这个区间中找出所有r>=x的node,我们维护了所有区间最大值,现在我们从线段树root出发,以dfs的思想遍历树上的的区间[1,rp],但是常规的dfs时间复杂度过高。我们这里dfs找的是r>=x的叶子,而线段树维护了区间最值,对于区间最值max<x的区间,一定没有我们要寻找的值,就不用进入这个子树。所以这优化成相当于二分查找的复杂度。

ps:这个整个过程体现了多个条件各个击破的思想,第一步对数组的排序找到了所有满足第一个条件的区间,第二步将r建树巧妙地利用了区间最值的思想优化了搜索(对dfs进行了剪枝)。这两步是把缠绕在一起的两个条件,变成了相互独立的两个条件,使他们可以先后分别考虑。

AC代码

要注意res乘法过程中可能超过int(没试过只是可能),而且最终结果有些样例会超过long long 所以用同余定理每步取余。

具体注意事项见代码注释。

#include<bits/stdc++.h>
using namespace std;
int const maxn=3e5+10;
typedef long long ll;
ll res,ans,ip,x;
ll sum[maxn<<2],book[maxn<<2];//book是从线段树叶子到co的映射 
#define lson l,(l+r)>>1,rt<<1
#define rson ((l+r)>>1)+1,r,rt<<1|1 //注意加括号,位运算优先级小于加法!!!
ll const mod=998244353;
ll const inf=0x7fffffffffffffff;//都用long long的话需要各种优化,在tle的边缘试探,(加了inline和register之后效率提高巨大) 

struct node{
    ll l,r,i,k;//i是第几次,k是下标
};
node co[maxn],*st[maxn];
int inline operator<(node a,node b){
    if(a.l==b.l)
        return a.r<b.r;
    return a.l<b.l;
}
void inline pushup(ll rt) {
    sum[rt]=max(sum[rt<<1],sum[rt<<1|1]); 

void build(ll l,ll r,ll rt){//rt是当前节点,l和r是当前节点表示范围 
    if(l==r){
        sum[rt]=co[ip].r;
        book[rt]=ip++;
        return;
    }
    build(lson);
    build(rson);
    pushup(rt);
}
void query(ll x,ll L,ll R,ll l,ll r,ll rt){//搜索点
    if(l==r){
        if(sum[rt]<x)return;//如果只有一个节点,那么线段树退化成一个点,一定满足l==r,所以要多判断一句区间最值 
        sum[rt]=-inf;
        co[book[rt]].i=ip;
        ans++;
        if(res==0)
            res=1;
        res*=co[book[rt]].k;
        res%=mod;
        return; 
    }
    if(L<=((l+r)>>1) && sum[rt<<1]>=x) 
        query(x,L,R,lson);
    if(R>((l+r)>>1) && sum[rt<<1|1]>=x)
        query(x,L,R,rson);
    pushup(rt);
}
bool cmp(node a,node b){
    return a.k<b.k;//升序 
}
int main(){
    int T,cs=1,m,n;
    scanf("%d",&T);
    ll y,rp;
    node t;
    while(T--){
        ip=1;
        res=x=0;
        cin>>m>>n;
        for(int register i=1;i<=m;i++){//long long 要用register 和 inline 优化 
            scanf("%lld%lld",&co[i].l,&co[i].r);
            co[i].k=i;    co[i].i=0;
        }
        sort(co+1,co+1+m);
        for(int register i=1;i<=m;i++)
            st[co[i].k]=&co[i];//不是这样做而是sort的话会tle掉至少10%的样例 
        build(1,m,1);
        printf("Case #%d:\n",cs++);
        for(int register i=1;i<=n;i++){
            ip=i;
            scanf("%lld",&y);
            x=y^(res%mod);
            res=ans=0;
            t.l=x;
            t.r=inf;
            rp=upper_bound(co+1,co+1+m,t)-co-1;
            if(rp>0)
                query(x,1,rp,1,m,1);
            printf("%lld\n",ans);
        }
        for(int register i=1;i<=m;i++){
            printf("%lld ",st[i]->i);//不是这样用指针做而是sort的话会tle掉至少10%的样例 
        }
        printf("\n");
    }
}

猜你喜欢

转载自blog.csdn.net/GreyBtfly/article/details/81535679