Monkeying Around 线段树+树状数组

题目大意,给定若干(Li, Ri, Ki)表示Li到Ri的人都得到一个标号为ki的球,依次进行。n个人每个人有一个状态0或者1,如果在这次操作前已经有过ki这种球了,那么状态会变成0,否则变成1.初始的时候都是0.求m次操作后有多少个0.
题解:一开始想错了,后来发现是个傻逼提。唯一需要考虑的性质是,每个人的最终状态 基 本 上 由最后一次覆盖到他的操作的ki决定。(题面是经过一点点转化的,所以现在看起来很显然)。
如果第i个人没有被任何区间覆盖,那么最后显然是0.
假设第i个人最后拿到的球的编号是ai,如果他拿了两次及以上的ai,那么状态是0,否则是1.
因此首先用线段树处理出每个人最后一个得到的球的编号。然后枚举球的编号x,把ki=x的区间+1,检查那些最后得到x的人时候只被覆盖一次即可,这部分显然树状数组一下即可。如果代码实现不好需要特判一下没有拿到球的人。注意多组数据。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define N 100010
#define lb(x) (x&-x)
#define solve(s,t,v) update(s,v),update(t+1,-v)
using namespace std;
struct segment{
    int l,r,v;
    segment *ch[2];
}*rt;int n;
struct node{
    int L,R;
    node(int l,int r)
    {
        L=l,R=r;
    }
    inline node operator=(const node &n)
    {
        L=n.L,R=n.R;return *this;
    }
};
vector<int> v[N];
vector<node> q[N];
int build(segment* &rt,int l,int r)
{
    rt=new segment;rt->l=l,rt->r=r,rt->v=0;
    if(l==r) return 0;int mid=(l+r)>>1;
    build(rt->ch[0],l,mid),build(rt->ch[1],mid+1,r);
    return 0;
}
int Clear(segment* &rt)
{
    int l=rt->l,r=rt->r;rt->v=0;if(l==r) return 0;
    return Clear(rt->ch[0]),Clear(rt->ch[1]);
}
inline int push_down(segment* &rt)
{
    rt->ch[0]->v=rt->v;
    rt->ch[1]->v=rt->v;
    return rt->v=0;
}
int update(segment* &rt,int s,int t,int v)
{
    int l=rt->l,r=rt->r,mid=(l+r)>>1;
    if(s<=l&&r<=t) return rt->v=v;
    if(rt->v) push_down(rt);
    if(s<=mid) update(rt->ch[0],s,t,v);
    if(mid<t) update(rt->ch[1],s,t,v);
    return 0;
}
int PUSH(segment* &rt)
{
    int l=rt->l,r=rt->r;
    if(l==r) return (l<=n?v[rt->v].push_back(l),0:0),0;
    if(rt->v) push_down(rt);
    PUSH(rt->ch[0]),PUSH(rt->ch[1]);
    return 0;
}
int c[N];
inline void update(int x,int v)
{
    for(;x<=n;x+=lb(x)) c[x]+=v;
}
inline int query(int x)
{
    int ans=0;
    for(;x;x-=lb(x)) ans+=c[x];
    return ans;
}
int main()
{
    int T;scanf("%d",&T);build(rt,1,N);
    while(T--)
    {
        int m;scanf("%d%d",&n,&m);
        Clear(rt);int maxl=0;
        for(int i=0;i<N;i++) v[i].clear(),q[i].clear();
        for(int i=1,x,l,k;i<=m;i++)
        {
            scanf("%d%d%d",&x,&l,&k),maxl=max(maxl,l);
            update(rt,max(1,x-k),min(n,x+k),l);
            q[l].push_back(node(max(1,x-k),min(n,x+k)));
        }
        PUSH(rt);int ans=0;
        for(int i=1;i<=maxl;i++)
        {
            for(int j=0;j<q[i].size();j++)
                solve(q[i][j].L,q[i][j].R,1);
            for(int j=0;j<v[i].size();j++)
                if(query(v[i][j])==1) ans++;
            for(int j=0;j<q[i].size();j++)
                solve(q[i][j].L,q[i][j].R,-1);
        }
        printf("%d\n",n-ans+v[0].size());
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值