P10587 「ALFR Round 2」C 小 Y 的数

题目描述

给定长为 \(n\) 的序列, \(m\) 次操作:

  • 1 l r x :将 \(l\sim r\) 中的数加上 \(x\)
  • 2 l r x :将 \(l\sim r\) 中的数乘上 \(x\)
  • 3 l r x :将 \(l\sim r\) 中的数修改为 \(x\)
  • 4 l r x :询问 \(l\sim r\)\(42,424,4242,42424,424242\) 出现次数之和。

数据范围

  • \(1\le n,m\le 5\cdot 10^5\)
  • \(1\le l\le r\le n,1\le a_i,x\le 5\cdot 10^5\) ,保证任意时刻 \(1\le a_i\le 5\cdot 10^5\)

时间限制 \(\texttt{700ms}\) ,空间限制 \(\texttt{64MB}\)

分析

先考虑如果只询问 \(M\) 的出现次数怎么做。

定义势能 \(\varphi\) 为线段树所有节点对应的区间中不超过 \(M\) 的数的个数之和,初始 \(\varphi=n\log n\)

对于操作 \(1,2\)\(\varphi\) 不增;对于操作 \(3\) ,将完全相同的区间视为单点, \(\varphi\) 至多增加 \(\mathcal O(\log n)\)

判断完全相同只需维护区间最大最小值。

对线段树每个节点维护 \(val=\max\limits_{[l,r]}[a_i\le M]a_i\) ,加乘直接对 \(val\) 打标记,如果 \(val\gt M\) 则递归下去修改。

回答询问只需顺便维护 \(val\) 的出现次数 \(cnt\) ,如果 \(val=M\) 则贡献为 \(cnt\) ,否则为零。

递归时我们花费 \(1\) 单位的代价使 \(\varphi\) 减少 \(1\) ,时间复杂度 \(\mathcal O((n+m)\log n)\)


同时开 \(5\) 棵线段树就做完了?抱歉,空间不够。

如果还想维护某个变量支持加乘操作,还能判断区间内是否某个位置越过了询问的数,容易发现这是做不到的。

退而求其次,注意到值域很小,所以乘法次数不会很多,直接暴力做。对于加法,记 \(nxt_i\)\(i\) 到下一个(\(\ge i\))询问的数的距离,对线段树每个节点维护 \(val=\min nxt_{a_i}\) ,加法和覆盖操作可以套用上面的做法。

于是我们需要重新定义势能:

  • \(\varphi_1\) 表示线段树所有区间中大于 \(a_i\) 的询问的数个数之和。
  • \(\varphi_2\) 表示线段树所有区间 \(\sum\lfloor\log a_i\rfloor\) 之和。

初始 \(\varphi_1=5n\log n,\varphi_2=n\log n\log V\)

对于加法操作,花费 \(1\) 的代价使 \(\varphi_1\) 减小 \(1\) ,同时 \(\varphi_2\) 不增。

对于乘法操作,花费 \(1\) 的代价使 \(\varphi_2\) 减小 \(1\) ,同时 \(\varphi_1\) 不增。

对于覆盖操作, \(\varphi_1\) 增加 \(5\)\(\varphi_2\) 增加 \(\log V\)

时间复杂度 \(\mathcal O((n\log n+m)(5+\log V))\)

#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int maxn=5e5+5;
int m,n;
int key[6]={42,424,4242,42424,424242,500001};
int a[maxn],nxt[maxn];
struct node
{
    int l,r;
    int mn,mx,cnt,val;///val=min nxt
    int add,cov;
}f[maxn<<2];
int read()
{
    int q=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) q=10*q+ch-'0',ch=getchar();
    return q;
}
void pushup(int p)
{
    f[p].mn=min(f[ls].mn,f[rs].mn);
    f[p].mx=max(f[ls].mx,f[rs].mx);
    f[p].val=min(f[ls].val,f[rs].val);
    f[p].cnt=(f[p].val==f[ls].val?f[ls].cnt:0)+(f[p].val==f[rs].val?f[rs].cnt:0);
}
void pushcov(int p,int x)
{
    f[p].mn=f[p].mx=x,f[p].val=nxt[x],f[p].cnt=f[p].r-f[p].l+1,f[p].cov=x,f[p].add=0;
}
void pushadd(int p,int x)
{
    if(f[p].cov) return pushcov(p,f[p].cov+x);
    assert(x<=f[p].val);
    f[p].mn+=x,f[p].mx+=x,f[p].val-=x,f[p].add+=x;
}
void pushdown(int p)
{
    assert(!f[p].cov||!f[p].add);
    if(f[p].cov) pushcov(ls,f[p].cov),pushcov(rs,f[p].cov),f[p].cov=0;
    if(f[p].add) pushadd(ls,f[p].add),pushadd(rs,f[p].add),f[p].add=0;
}
void build(int p,int l,int r)
{
    f[p].l=l,f[p].r=r;
    if(l==r) return f[p].mn=f[p].mx=a[l],f[p].val=nxt[a[l]],f[p].cnt=1,void();
    int mid=(l+r)>>1;
    build(ls,l,mid),build(rs,mid+1,r);
    pushup(p);
}
void modify_a(int p,int l,int r,int x)
{
    if(l>f[p].r||r<f[p].l) return ;
    if(l<=f[p].l&&f[p].r<=r)
    {
        if(f[p].mn==f[p].mx) return pushcov(p,f[p].mn+x);
        if(x<=f[p].val) return pushadd(p,x);
    }
    pushdown(p);
    modify_a(ls,l,r,x),modify_a(rs,l,r,x);
    pushup(p);
}
void modify_m(int p,int l,int r,int x)
{
    if(x==1||l>f[p].r||r<f[p].l) return ;
    if(l<=f[p].l&&f[p].r<=r&&f[p].mn==f[p].mx) return pushcov(p,f[p].mn*x);
    pushdown(p);
    modify_m(ls,l,r,x),modify_m(rs,l,r,x);
    pushup(p);
}
void modify_c(int p,int l,int r,int x)
{
    if(l>f[p].r||r<f[p].l) return ;
    if(l<=f[p].l&&f[p].r<=r) return pushcov(p,x);
    pushdown(p);
    modify_c(ls,l,r,x),modify_c(rs,l,r,x);
    pushup(p);
}
int query(int p,int l,int r)
{
    if(l>f[p].r||r<f[p].l) return 0;
    if(l<=f[p].l&&f[p].r<=r) return !f[p].val?f[p].cnt:0;
    pushdown(p);
    return query(ls,l,r)+query(rs,l,r);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=5e5;i++)
        for(int j=5;j>=0;j--)
            if(i<=key[j]) nxt[i]=key[j]-i;
    build(1,1,n);
    while(m--)
    {
        int op=read(),l=read(),r=read();
        if(op==1) modify_a(1,l,r,read());
        if(op==2) modify_m(1,l,r,read());
        if(op==3) modify_c(1,l,r,read());
        if(op==4) printf("%d\n",query(1,l,r));
    }
    return 0;
}

总结

  • 本题卡 scanf ,必须快读。

  • 标记合并是永远的难点。由于 cov 优先级比 add 高,所以如果 cov 标记存在,区间加要转化为区间覆盖操作。

  • 惨痛的 debug 经历:

    博主在第 \(55\) 行后加上了这样一句话 if(f[p].l==f[p].r) return f[p].mn+=x,f[p].mx+=x,f[p].val=nxt[f[p].mn],void(); ,然后报错信息都出在几千行的位置。再加上本题数据超级难造(纯随机小数据输出全是零,大数据很难保证值域范围),调了一个多小时。

    后来发现 cov 标记也要更新,因为头顶的 add 标记推到这里会转化为区间覆盖,需要用到 cov 标记的值。

    一般情况下标记都是打给下一层的,但是本题我们需要用 cov 标记反推这一层的信息,总之非常魔幻就对了。

posted @ 2025-01-03 09:54  peiwenjun  阅读(2)  评论(0编辑  收藏  举报