[codeforces896E] Welcome home, Chtholly

8 篇文章 0 订阅
3 篇文章 0 订阅

题目大意

给定一个n个数的序列。m次操作:1. 给区间[l,r]中所有大于x的数减x 2. 询问区间[l,r]中数值为x的数个数。

n,m≤100000 1≤a[i],x≤100000

分析

这题?循环展开然后optimize一下就过了

这是由乃题,我们应该考虑分块。

值域范围比较小,我们可以直接记录cnt[i][j]表示块i中数值为j的数个数。
然后再用并查集把同一块中数值相同的元素缩在一起。
对于第一个操作:首先两边的散块可以暴力修改。
对于中间的块:设mx表示该块中的最大值。分以下几种情况:
1. x≥mx,此时不用做任何修改
2. x < mx < 2*x,此时可以暴力把区间[x+1,mx]和[1,mx-x]对应的元素合并
3. 2*x≤mx,我们注意到:第一个操作等价于先把所有元素减x,然后把小于等于0的加x。所以我们可以设一个tag[i]表示块i中这种情况减少的总数。对于这种情况,先暴力把区间[1,x]和[x+1,x+x]对应的元素合并,然后tag[i]+=x

第二个操作:两边的散块暴力查询,中间的ans+=cnt[i][x+tag[i]]即可。

我们发现,对于修改操作:每一块合并的次数不超过100000次。
所以整体时间复杂度是 O(mn)

#include <cstdio>
#include <cstring>
#include <algorithm>

#define max(a,b) ((a)>(b)?(a):(b))

using namespace std;

const int N=1e5+5,G=320;

typedef long long LL;

int n,m,f[N],cnt[N],Id[N],val[N],b[N],tag[G],a[314][N],mx[G],ans;

char c;

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

int Get(int x)
{
    return (f[x]==x)?x:f[x]=Get(f[x]);
}

void Merge(int x,int y)
{
    f[y]=x; cnt[x]+=cnt[y];
}

void Change1(int l,int r,int x)
{
    int i,j;
    for (j=l;Id[j-1]==Id[j];j--);
    for (i=j;Id[i]==Id[r];i++)
    {
        b[i]=val[Get(i)]; a[Id[i]][b[i]]=0;
    }
    mx[Id[r]]=0;
    for (i=l;i<=r;i++) if (b[i]-tag[Id[i]]>x) b[i]-=x;
    for (i=j;Id[i]==Id[r];i++)
    {
        f[i]=i; cnt[i]=1; val[i]=b[i];
        mx[Id[i]]=max(mx[Id[i]],val[i]);
        if (!a[Id[i]][val[i]]) a[Id[i]][val[i]]=i;else Merge(a[Id[i]][val[i]],i);
    }
}

void Change2(int l,int r,int x)
{
    for (int i,j=l;j<=r;j++) if (tag[j]+x<mx[j])
    {
        if (tag[j]+(x<<1)<=mx[j])
        {
            for (i=tag[j]+x;i>tag[j];i--) if (a[j][i])
            {
                if (!a[j][i+x]) val[a[j][i+x]=a[j][i]]=i+x;else Merge(a[j][i+x],a[j][i]);
                a[j][i]=0;
            }
            tag[j]+=x;
        }else
        {
            for (i=tag[j]+x+1;i<=mx[j];i++) if (a[j][i])
            {
                if (!a[j][i-x]) val[a[j][i-x]=a[j][i]]=i-x;else Merge(a[j][i-x],a[j][i]);
                a[j][i]=0;
            }
            for (i=tag[j]+x;!a[j][i];i--);
            mx[j]=i;
        }
    }
}

int main()
{
    n=read(); m=read();
    for (int i=1;i<=n;i++)
    {
        Id[i]=i/G; f[i]=i; cnt[i]=1;
        val[i]=read(); mx[Id[i]]=max(mx[Id[i]],val[i]);
        if (!a[Id[i]][val[i]]) a[Id[i]][val[i]]=i;else Merge(a[Id[i]][val[i]],i);
    }
    Id[0]=Id[n+1]=-1;
    for (int typ,l,r,x,i;m--;)
    {
        typ=read(); l=read(); r=read(); x=read();
        if (typ==1)
        {
            if (Id[l]==Id[r])
            {
                Change1(l,r,x);
                continue;
            }
            for (i=l;Id[i+1]==Id[l];i++);
            Change1(l,i,x);
            for (i=r;Id[i-1]==Id[r];i--);
            Change1(i,r,x);
            Change2(Id[l]+1,Id[r]-1,x);
        }else
        {
            ans=0;
            if (Id[l]==Id[r])
            {
                for (i=l;i<=r;i++) ans+=(val[Get(i)]-tag[Id[i]]==x);
            }else
            {
                for (i=l;Id[i]==Id[l];i++) ans+=(val[Get(i)]-tag[Id[i]]==x);
                for (i=r;Id[i]==Id[r];i--) ans+=(val[Get(i)]-tag[Id[i]]==x);
                for (i=Id[l]+1;i<Id[r];i++) if (x+tag[i]<N) ans+=cnt[a[i][x+tag[i]]];
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值