冷门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\n",ans);
}
void IsMyWife()
{
Input();
Sakuya();
}
}
#undef int //long long
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();
return 0;
}