冷门trick:线性区间单调求和

冷门trick:线性区间单调求和

要解决的问题,见 这里 的描述。

这个trick不太好自己yy,这里直接开讲:我们任取一个 (p),作为分界线,并保证这个 (p) 被包含在当前的区间里面。

对于 (p) 左边的东西,我们维护一个后缀和,每次直接查询。对于 (p) 右边的东西,我们用一个单调的东西 (cur),每次 (r)​ 增加的时候,一个个加上去。那对于当前区间,答案就是左边的答案+右边的答案。

有一个问题是,区间不断的右移,如果哪一天 (p) 跑到区间左边了,咋办?

此时我们把 (p) 移动到当前区间的右端点,重构后缀和。为保证复杂度,我们记录 (p) 上次的位置 (p')​,然后只需求出 ((p',p]) 这一段的后缀和即可。为啥只需要这一部分,因为我们动 (p) ,说明 (l>p'),而 (l) 是递增的,所以更左边的信息我们 再也不需要 了,直接不管。而只有 (p')(p)​ 这一段新增的,需要重构一下后缀和。顺便再令 (cur) 为一个不影响答案的元素。比如加法中的 (0),乘法中的 (1),单位矩阵,或者是求min/max时的(pm infin)

分析复杂度:

  • 对于 (cur),因为它的位置只会增,所以均摊是 (O(n))
  • 对于 (p),它会动很多次,但是复杂度是每次动的长度(即 (p-p'))的和,发现它也是 (O(n))

于是总复杂度严格线性。

对于洛谷上那个我出的板子题,参考代码如下:

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
    #define N   10000007
    #define mod 59861874
    #define int long long
    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Fs(i,l,r,c) for(int i=l;i<=r;c)
    #define Ds(i,r,l,c) for(int i=r;i>=l;c)
    #define MEM(x,a) memset(x,a,sizeof(x))
    #define FK(x) MEM(x,0)
    #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
    #define p_b push_back
    #define sz(a) ((int)a.size())
    #define all(a) a.begin(),a.end()
    #define iter(a,p) (a.begin()+p)
    #define PUT(a,n) F(i,1,n) printf("%d ",a[i]); puts("");
    int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
    template <typename T> void Rd(T& arg){arg=I();}
    template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
    void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
    int n,q,seed;
    void Input()
    {
        n=I(); q=I(); seed=I();
    }
    int uu=1;
    int rnd() {uu=uu*seed%100003; return (uu<<5)|(uu<<4)|(uu);}
    int rrnd(int l,int r) {return rnd()%(r-l+1)+l;}
    int a[N],ll[N],rr[N];
    void gen()
    {
        for(int i=1;i<=n;++i)
        {
            a[i]=rrnd(1,2000);
        }
        int x=0,y=0;
        for(int i=1;i<=q;++i)
        {
            x=x+rrnd(1,10); y=max(y,x);
            int t=rnd();
            if (t%3) y=y+rrnd(1,50); 
            else y=y+rrnd(1,400);
            x=min(x,n); y=min(y,n); 
            ll[i]=x; rr[i]=y;
            assert(ll[i]>=ll[i-1]); assert(rr[i]>=rr[i-1]);
        }
    }

    int suf[N];
    void Sakuya()
    {
        gen();

        int las=0,pos=0; // pos:分界点p; las:上一次在哪
        int cur=1,curp=0; // cur:右边那一段的“和”; curp:处理到了哪
        int ans=1,mod2=998244353;
        F(tt,1,q)
        {
            int l=ll[tt],r=rr[tt];
            
            if (l>pos) // l>pos, 此时需要重构后缀和
            {
                las=pos; pos=r; 
                cur=1,curp=r; // cur设置为一个无影响元, 这里令它为1, 因为是乘法
                suf[pos]=a[r]; D(i,pos-1,las+1) suf[i]=suf[i+1]*a[i]%mod;
                // 只重构 (las,pos] 段的前缀和
            }
            else
            {
                while(curp<r) ++curp,cur=cur*a[curp]%mod; // 单调的动cur即可
            }
            // 左边的答案就是suf[l], 查询后缀和; 右边的答案是cur, 单调的加上去就行
            int ansi=cur*suf[l]%mod;
            ans=ans*(114514ll*tt%mod2+(1919810^ansi))%mod2; // 神秘输出格式
        }
        printf("%lld
",ans);
    }
    void IsMyWife()
    {
        Input();
        Sakuya();
    }
}
#undef int //long long
int main()
{
    Flandre_Scarlet::IsMyWife();
    getchar();
    return 0;
}
原文地址:https://www.cnblogs.com/LightningUZ/p/15115111.html