[bzoj4373]算术天才⑨与等差数列

//被标题吸引进来,无论如何都要切出来

题目大意

给你一个长度为n的序列,有m个操作,写一个程序支持以下两个操作:
1. 修改一个值
2. 给出三个数l,r,k,询问:如果把区间[l,r]的数从小到大排序,能否形成公差为k的等差数列。
强制在线

数据范围

n,m≤300000 0≤k,a[i]≤ 109

分析

首先特判k=0和l=r的情况。

然后考虑不满足上面两个条件的情况。
直接维护区间等差数列显然很难,那么考虑一下:如果区间[l,r] (l < r)排序后能形成公差为k(k>0)的等差数列,要满足什么条件?
1. 很显然,假设min是区间最小值,max是区间最大值,那么 min+k(rl)=max
2. 区间相邻两个数之差的绝对值的gcd=k,可以自行脑补一下
3. 区间没有重复的数

维护区间信息

上述的前两个条件可以直接用线段树维护。
现在看第3个条件。
首先,序列出现过的值最多只有600000种,所以可以对于每个值开一个set,对应的id用一个map存起来。
然后维护一个pre[i],表示当前a[i]这个值,在i前面最后一次出现的位置。那么满足第3个条件,当且仅当区间[l,r]的pre的最大值小于l。这个也是用线段树维护。
然后看修改操作:在set上找前一个数、后一个数,然后修改相应的值。
时间复杂度 O(nlogn) ,要注意优化常数。

/**************************************************************
    Problem: 4373
    User: worldwide
    Language: C++
    Result: Accepted
    Time:6148 ms
    Memory:53008 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
#include <set>

#define id it->second

using namespace std;

const int maxn=300005,maxt=1048578,maxm=600005;

typedef long long LL;

int n,m,cnt,Min[maxt],Max[maxt],a[maxn],Max_pre[maxt],pre[maxn],next[maxn],Gcd[maxt],tot,l,r;

int mininum,maxinum,G;

bool flag;

char c;

map <int,int> num;

set <int> tree[maxm];

int read()
{
    for (c=getchar();c<'0' || c>'9';c=getchar());
    int x=c-48;
    for (c=getchar();c>='0' && c<='9';c=getchar()) x=x*10+c-48;
    return x;
}

int gcd(int x,int y)
{
    if (x==0) return y;
    if (y==0) return x;
    return gcd(y,x%y);
}

void init(int l,int r,int x)
{
    if (l==r)
    {
        Min[x]=Max[x]=a[l]; Max_pre[x]=pre[l];
        if (l<n) Gcd[x]=abs(a[l]-a[l+1]);else Gcd[x]=1;
        return;
    }
    int mid=l+r>>1;
    init(l,mid,x<<1); init(mid+1,r,x<<1|1);
    Min[x]=min(Min[x<<1],Min[x<<1|1]);
    Max[x]=max(Max[x<<1],Max[x<<1|1]);
    Max_pre[x]=max(Max_pre[x<<1],Max_pre[x<<1|1]);
    Gcd[x]=gcd(Gcd[x<<1],Gcd[x<<1|1]);
}

void change_pre(int l,int r,int g,int New,int x)
{
    if (l==r)
    {
        Max_pre[x]=pre[l]=New;
        return;
    }
    int mid=l+r>>1;
    if (g<=mid) change_pre(l,mid,g,New,x<<1);else change_pre(mid+1,r,g,New,x<<1|1);
    Max_pre[x]=max(Max_pre[x<<1],Max_pre[x<<1|1]);
}

void change_a(int l,int r,int g,int x)
{
    if (l==r)
    {
        Min[x]=Max[x]=a[l];
        return;
    }
    int mid=l+r>>1;
    if (g<=mid) change_a(l,mid,g,x<<1);else change_a(mid+1,r,g,x<<1|1);
    Min[x]=min(Min[x<<1],Min[x<<1|1]);
    Max[x]=max(Max[x<<1],Max[x<<1|1]);
    Max_pre[x]=max(Max_pre[x<<1],Max_pre[x<<1|1]);
}

void change_gcd(int l,int r,int g,int x)
{
    if (l==r)
    {
        Gcd[x]=abs(a[l]-a[l+1]);
        return;
    }
    int mid=l+r>>1;
    if (g<=mid) change_gcd(l,mid,g,x<<1);else change_gcd(mid+1,r,g,x<<1|1);
    Gcd[x]=gcd(Gcd[x<<1],Gcd[x<<1|1]);
}

void query(int l,int r,int a,int b,int x)
{
    if (l==a && r==b)
    {
        if (Max_pre[x]>=l) flag=1;
        if (Min[x]<mininum) mininum=Min[x];
        if (Max[x]>maxinum) maxinum=Max[x];
        return;
    }
    int mid=l+r>>1;
    if (b<=mid) query(l,mid,a,b,x<<1);
    else if (a>mid) query(mid+1,r,a,b,x<<1|1);
    else
    {
        query(l,mid,a,mid,x<<1); query(mid+1,r,mid+1,b,x<<1|1);
    }
}

void query_gcd(int l,int r,int a,int b,int x)
{
    if (l==a && r==b)
    {
        G=gcd(G,Gcd[x]);
        return;
    }
    int mid=l+r>>1;
    if (b<=mid) query_gcd(l,mid,a,b,x<<1);else
    if (a>mid) query_gcd(mid+1,r,a,b,x<<1|1);else
    {
        query_gcd(l,mid,a,mid,x<<1); query_gcd(mid+1,r,mid+1,b,x<<1|1);
    }
}

int main()
{
    n=read(); m=read();
    for (int i=1;i<=n;i++)
    {
        a[i]=read();
        map <int,int> ::iterator it=num.find(a[i]);
        if (it==num.end())
        {
            num.insert(make_pair(a[i],++tot));
            tree[tot].insert(i);
            pre[i]=0;
        }else
        {
            tree[id].insert(i);
            set <int> ::iterator ii=tree[id].find(i); ii--;
            pre[i]=*ii; next[*ii]=i;
        }
    }
    init(1,n,1);
    while (m--)
    {
        int op=read();
        if (op==1)
        {
            int x=read()^cnt,y=read()^cnt;
            if (a[x]==y) continue;
            map <int,int> ::iterator it=num.find(a[x]);
            set <int> ::iterator ii=tree[id].find(x);
            if (pre[x]>0)
            {
                next[pre[x]]=next[x];
                if (next[x]>0) change_pre(1,n,next[x],pre[x],1);
            }else if (next[x]>0) change_pre(1,n,next[x],0,1);
            tree[id].erase(ii);
            it=num.find(y);
            if (it==num.end())
            {
                num.insert(make_pair(y,++tot));
                tree[tot].insert(x);
                pre[x]=next[x]=0;
            }else
            {
                tree[id].insert(x);
                ii=tree[id].find(x);
                if (ii!=tree[id].begin())
                {
                    ii--;
                    next[x]=next[*ii];
                    if (next[x]>0) change_pre(1,n,next[x],x,1);
                    next[*ii]=x;
                    pre[x]=*ii;
                }
            }
            a[x]=y;
            change_a(1,n,x,1);
            if (x>1) change_gcd(1,n,x-1,1);
            if (x<n) change_gcd(1,n,x,1);
        }else
        {
            l=read()^cnt; r=read()^cnt; int k=read()^cnt;
            if (l==r)
            {
                printf("Yes\n"); cnt++; continue;
            }
            mininum=(int)1e9; maxinum=flag=0;
            query(1,n,l,r,1);
            if (!k)
            {
                if (mininum==maxinum)
                {
                    printf("Yes\n"); cnt++;
                }else printf("No\n");
                continue;
            }
            G=k;
            query_gcd(1,n,l,r-1,1);
            if (flag || mininum+(LL)k*(r-l)!=maxinum || G<k)
            {
                printf("No\n");
            }else
            {
                printf("Yes\n"); cnt++;
            }
        }
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值