bzoj 4631: 踩气球 并查集+主席树

Description
六一儿童节到了, SHUXK 被迫陪着M个熊孩子玩一个无聊的游戏:
N N N个盒子从左到右排成一排,第 i i i个盒子里装着 A i A_i Ai个气球。
SHUXK 要进行 Q Q Q次操作,每次从某一个盒子里拿出一个没被踩爆的气球,然后熊孩子们就会立刻把它踩爆。
M M M个熊孩子每个人都指定了一个盒子区间 [ L i , R i ] [L_i, R_i] [Li,Ri]。 如果某一个时刻,一个熊孩子发现自己选定的盒子区间 [ L i , R i ] [L_i, R_i] [Li,Ri]中的所有气球都已经被踩爆了,他就会非常高兴(显然之后他一直会很高兴)。
为了不辜负将自己的任务强行塞给 SHUXK 的那个人的期望, SHUXK 想向你询问:
他每次操作过后会有多少个熊孩子很高兴。
Input

第一行包含两个正整数 N N N M M M,分别表示盒子和熊孩子的个数。
第二行包含 N N N个正整数 A i ( 1 &lt; = A i &lt; = 1 0 5 ) A_i( 1 &lt; = A_i &lt; = 10^5) Ai1<=Ai<=105,表示每个盒子里气球的数量。
以下M行每行包含两个正整数 L i , R i ( 1 &lt; = L i &lt; = R i &lt; = N ) L_i, R_i( 1 &lt; = L_i &lt; = R_i &lt; = N) Li,Ri1<=Li<=Ri<=N,分别表示每一个熊孩子指定的区间。
以下一行包含一个正整数 Q Q Q,表示 SHUXK 操作的次数。
以下 Q Q Q行每行包含一个正整数 X X X,表示这次操作是从第 X X X个盒子里拿气球。为
了体现在线,我们对输入的 X X X进行了加密。
假设输入的正整数是 x ′ x&#x27; x,那么真正的 X = ( x ′ + L a s t a n s − 1 ) m o d &ThinSpace;&ThinSpace; N + 1 X = (x&#x27; + Lastans − 1)\mod N + 1 X=(x+Lastans1)modN+1。其
中Lastans为上一次询问的答案。对于第一个询问, L a s t a n s = 0 Lastans = 0 Lastans=0
输入数据保证 1 &lt; = x ′ &lt; = 1 0 9 1 &lt; = x&#x27; &lt; = 10^9 1<=x<=109, 且第X个盒子中有尚未被踩爆的气球。
N &lt; = 1 0 5 , M &lt; = 1 0 5 , Q &lt; = 1 0 5 N &lt; = 10^5 ,M &lt; = 10^5 ,Q &lt; = 10^5 N<=105,M<=105,Q<=105

Output

包含 Q Q Q行,每行输出一个整数,表示 SHUXK 一次操作后询问的
答案。答案的顺序应与输入数据的顺序保持一致。
Sample Input
5 3
1 1 1 1 1
5 5
2 2
1 3
5
4
2
5
2
3

Sample Output
0
1
1
2
3

【样例说明】
实际上每次操作的盒子是: 4 2 1 3 5
在第二次操作后,第二个熊孩子会高兴 (区间[2,2]中的气球已经全部被踩爆)。
在第四次操作后,第三个熊孩子会高兴(区间[1,3]中的气球已经全部被踩爆)。
在第五次操作后,第一个熊孩子会高兴(区间[5,5]中的气球已经全部被踩爆)。

分析:
可以暴力修改,只有当某一位减至 0 0 0 时,答案才有可能出现变化。
考虑新增的区间数,显然只有当这个区间只有这一位不是 0 0 0时才有贡献。
对于每一个位置 x x x,考虑他左边和右边的全 0 0 0区间长度,记为 L L L R R R。这个可以使用并查集进行维护,可以通过维护集合大小来算出。
新增的答案就是左边界在 [ x − L , x ] [x-L,x] [xL,x],右边界在 [ x , x + R ] [x,x+R] [x,x+R]的区间个数。可以给区间按左边界排序,使用主席树。每个右边界权值位置+1,然后求和即可。
复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)的。

代码:

/**************************************************************
    Problem: 4631
    User: ypxrain
    Language: C++
    Result: Accepted
    Time:1416 ms
    Memory:27464 kb
****************************************************************/
 
#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>
 
const int maxn=1e5+7;
 
using namespace std;
 
int n,m,T,x,ans,cnt;
int a[maxn],root[maxn],num[maxn],p[maxn],val[maxn];
 
struct rec{
    int l,r;
}e[maxn];
 
struct node{
    int l,r,data;
}t[maxn*20];
 
void ins(int &p,int q,int l,int r,int x,int k)
{
    if (!p) p=++cnt;
    t[p].data=t[q].data+1;
    if (l==r) return;
    int mid=(l+r)/2;
    if (x<=mid) t[p].r=t[q].r,ins(t[p].l,t[q].l,l,mid,x,k);
           else t[p].l=t[q].l,ins(t[p].r,t[q].r,mid+1,r,x,k);
          
}
 
int getsum(int p,int q,int l,int r,int x,int y)
{
    if ((l==x) && (r==y)) return t[p].data-t[q].data;
    int mid=(l+r)/2;
    if (y<=mid) return getsum(t[p].l,t[q].l,l,mid,x,y);
    else if (x>mid) return getsum(t[p].r,t[q].r,mid+1,r,x,y);
    else return getsum(t[p].l,t[q].l,l,mid,x,mid)+getsum(t[p].r,t[q].r,mid+1,r,mid+1,y);
}
 
int find(int x)
{
    if (!p[x]) return x;
    return p[x]=find(p[x]);
}
 
void uni(int x,int y)
{
    int u=find(x),v=find(y);
    if (u==v) return;
    p[u]=v;
    val[v]+=val[u];
}
 
bool cmp(rec a,rec b)
{
    return a.l<b.l;
}
 
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=m;i++) scanf("%d%d",&e[i].l,&e[i].r);
    sort(e+1,e+m+1,cmp);
    int k=1;
    for (int i=1;i<=n;i++)
    {
        while (e[k].l==i)
        {
            ins(root[k],root[k-1],1,n,e[k].r,1);
            k++;
        }
        num[i]=k-1;
    }
    scanf("%d",&T);
    int l,r;        
    for (int i=1;i<=T;i++)
    {
        scanf("%d",&x);
        x=(x+ans-1)%n+1;
        a[x]--;
        if (!a[x])
        {
            val[x]++;
            l=val[find(x-1)],r=val[find(x+1)];
            if ((x>1) && (!a[x-1])) uni(x-1,x);
            if ((x<n) && (!a[x+1])) uni(x,x+1);
            ans+=getsum(root[num[x]],root[num[x-l-1]],1,n,x,x+r);
        }
        printf("%d\n",ans);
    }
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值