2021 ICPC昆明 M.Stone Games(主席树)

题意:

给出一个长度为 n n n 的数组, m m m 次询问,询问给出一个区间 ( l , r ) (l,r) (l,r),求这个区间的数任意相加,不能得到的最小的和是多少,询问采用强制在线。

题解:

先考虑给定一个集合,怎么求不能得到的最小的和。

1. 1. 1. 如果集合中不存在 1 1 1 ,那么答案就是 1 1 1

2. 2. 2. 否则,设 集合中的数 b 1 ≤ b 2 ≤ b 3 ≤ . . . ≤ b k b_1 \leq b_2 \leq b_3 \leq ... \leq b_k b1b2b3...bk , s i = ∑ j = 1 i b j s_i= \sum\limits_{j=1}^{i}b_j si=j=1ibj

假设前 i i i 个数能构成 ( 1 , x ) (1,x) (1,x) ,如果 b i + 1 ≤ x + 1 b_{i+1} \leq x+1 bi+1x+1 ,那么就一定能构成 ( 1 , b i + 1 + x ) (1,b_{i+1}+x) (1bi+1+x) 。为什么?如果 b i + 1 b_{i+1} bi+1 大于 x + 1 x+1 x+1 ,那么 b i + 1 b_{i+1} bi+1 不管怎么加,得到的数永远都会 ≥ b i + 1 \geq b_{i+1} bi+1 ,那么 x + 1 x+1 x+1 就不能构成,即不能连续下去。

由此说明,其实 x x x 就等于 s i s_i si 。也就是说,我们只需每次找到 ( 1 , x + 1 ) (1,x+1) (1x+1) 内的所有数,然后求和,假如 s u m = = x sum==x sum==x ,那么更新不了 ,否则就可以更新为 ( 1 , s u m ) (1,sum) (1,sum) 。那么怎么找某段区间的值在某段区间的和呢?这就是经典的主席树维护一下即可。

还有一点,在求答案时,暴力更新 ( 1 , x ) (1,x) (1x)即可,因为 每次更新都是类似于斐波那契这样加起来,而数值范围在 1 e 9 1e9 1e9内,差不多就是 l o g log log 级别的复杂度。总的时间复杂度为 O ( n l o g l o g ) O(nloglog) O(nloglog)
代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<ctime>
#define iss ios::sync_with_stdio(false)
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int mod=1e9+7;
const int MAXN=1e6+5;
const int inf=0x3f3f3f3f;
int a[MAXN];
int tree[MAXN];
int cnt=0;
std::vector<int> v;
int len;
struct Node
{
    int l,r;
    ll sum;
}node[MAXN*40];
int cal(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int cal_rk(ll x)
{
    if(v[len-1]<=x) return len;
    int pos=upper_bound(v.begin(),v.end(),x)-v.begin()+1;
    pos--;
    return pos;
}
void insert(int l,int r,int pre,int &now,int x,int val)
{
    node[++cnt]=node[pre];
    now=cnt;
    node[now].sum+=val;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(x<=mid) insert(l,mid,node[pre].l,node[now].l,x,val);
    else insert(mid+1,r,node[pre].r,node[now].r,x,val);
}
ll query(int l,int r,int pre,int now,int find_right)
{
    if(r<=find_right) return node[now].sum-node[pre].sum;
    ll ans=0;
    int mid=(l+r)>>1;
    if(l<=find_right) ans+=query(l,mid,node[pre].l,node[now].l,find_right);
    if(mid+1<=find_right) ans+=query(mid+1,r,node[pre].r,node[now].r,find_right);
    return ans;   
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        v.push_back(a[i]);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    len=v.size();
    for(int i=1;i<=n;i++)
    {
        insert(1,n,tree[i-1],tree[i],cal(a[i]),a[i]);
    }
    ll last=0;
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        l=(l+last)%n+1;
        r=(r+last)%n+1;
        if(l>r) swap(l,r);
        ll x=0;
        while(1)
        {
            int p=cal_rk(x+1);
            if(p==0) break;
            ll sum=query(1,n,tree[l-1],tree[r],p);
            if(sum==x) break;
            else x=sum;
        }
        printf("%lld\n",x+1);
        last=x+1;
    }

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值