【学术篇】规律选手再次证明自己(舒老师的胡策题 T2 LX还在迷路)
只要你不强制在线, 我就能分块.
——Reflash
就算你强制在线, 我还是要分块.
——Enzymii
今天做了一波舒老师的毒瘤题, T1据说很水但是没思路所以直接放掉了..
去看了看T2好像可以水不错的分数, 那就去做一下.
我们先把题目放一下: (画风很迷很迷 大家稍微忍一下 忍一下)
就很显然一道数据结构题... 本来想写线段树, 但是标记看上去好像并不怎么好维护, 看一下数据范围, \(n\leq100000\)? 那我们可以分块啊~
题目的输入格式中有一行小字说的是: "想用离线分块水过去? 不存在的" 然后成功被窝打脸...但其实用的是在线分块...
那么具体要怎么写呢? 我们看一下这些操作...
这里默认看下去的都知道基础的分块怎么写了哈~
我们先来看这个奇怪的修改操作.
我们接下来会各种用到
所以我们做一个宏定义就是偷个懒, 用一个\(s(x)\)来表示前x个正整数的和..
那么这个修改操作是等价于将区间\([L,R]\)中每个数\(X\)加\(s(X-L+1)\)的..
对于一次操作, 不满一个块的当然是暴力, 所以我们只需要考虑整块的情况.
我们令\(L\)编号为\(1\), \(R\)为\(R-L+1\), 这样每个点就相当于加了一个1到这个点编号的前缀和.
首先非常明确的是这个前缀和是可以O(1)求出的~
我们假设一个要处理的整块的左端点的编号为\(x\).
然后可以明确的是下一个点比它大\(x+1\), 再下一个点比前一个点大\(x+2\), 再后面是\(x+3\)... ,以此类推.
如果只是这样的话我们记录一下这个块的首项增加的数量(就是\(s(x)\)啦)(这东西我管它叫标记1), 和块左端点在操作区间的编号\(x\)(这个就叫标记2吧), 就可以\(O(1)\)推出每个点的实际值了...
但是我们要打的标记不止一个... 我们假设这个要处理的块之前的标号有一个\(x\)了, 我们要再加一个标号\(y\).
我们当然不能开个数组(vector)存每个标记, 就要考虑标记的合并问题.
我们可以先看块的左端点看出原来这个点的增加的值应该是\(s(x)\), 现在这个值要变成\(s(x)+s(y)\), 这个变化很容易, 我们把标记1更改下就行, 复杂度\(O(1)\)
同时后面的点要从\(s(x+1)\)变成\(s(x+1)+s(y+1)\)... 根据公式我们可以得出现在这一项比前一项大了\(x+y+2\).
那么以此类推再下一个点比前一个点大\(x+y+4\), 再然后是\(x+y+6\)...
我们发现\(x\)与\(y\)总是简单相加的, 所以标记2也可以直接相加.
但是我们发现常数项跟原来相比扩大了2倍... 所以大概也必须记录一下这个常数项.
但是常数项的规律我们还没完全摸透(比如我们无法确定是线性关系还是指数关系(但究竟什么关系不是很显然么))
那我们再加一个标号\(z\), 看一下变化.. 发现常数项变成了原来的3倍...
所以我们惊奇地发现, 这个常数项其实就是这个块加上的标号的个数, 我们再用一个标记维护这玩意就行了(叫标记3好了)
总结一下, 我们对每个完整的块维护四个标记:
- 块的首项增加的数
- 块左端点在当前操作区间的编号
- 块被操作区间完整包含的次数
- 还有一个最普通的, 不含特殊标记的区间和
当我们进行一次修改操作时, 对于不完整的块, 直接暴力修改单点值, 顺带维护标记4.
而对于每个完整的块, 设左端点在这个区间的编号为\(x\)的话:
- 标记1 +s(x)
- 标记2 +x
- 标记3 +1
这样就维护完了.
我们再来看查询操作.
查询的时候, 对于不完整的块依然是暴力加和..
那么如何通过标记计算一个单点的值?
我们可以知道, 在这个点所在的块上, 维护着一堆标记来记录这个点增加了多少.
所以这个单点的值=基础值(就是暴力修改的时候改的值)+通过标记计算出的增加值.
我们通过上面的分析知道, 这个块的首项增加了[1]([x]表示标记x的值), 而这个块里的后一项比前一项要多[2]+[3]×(块内编号-1)..
综上可以得出
其中\(now\)表示最终结果, \(a\)表示基础值, \(l\)表示区间的左端点. 这样算是\(O(1)\)的, 就不会影响复杂度.
那么完整的块怎么算呢?
很像.. 我们只要对每一项都求和就行了.
其中后面的那一堆我们可以\(O(n)\)预处理... 所以计算起来也是\(O(1)\)的..
这样我们只要分\(\sqrt n\)大小的块, 就可以保证复杂度在\(O(q\sqrt n)\)了...
那么我们就做完了... 分块码起来还是比线段树简单些的...
像我码力这么弱的人要是写线段树考场上怕是完全debug不出来咯...
害怕舒老师卡我我还丧心病狂地压了一波常数(然而事实证明真的很难卡OvO), 于是代码变得很不好看... 大家凑合着看吧..
//不特别加注释了, 就说说数组的含义吧
//frt是标记1 adum是标记2 cnt是标记3 sum是标记4(基础数组区间和) a是基础数组
#include <cmath>
#include <cstdio>
#include <algorithm>
const int N=120020;
const int p=1e9+7;
const int M=1000;
int bl[N],frt[M],adum[M],sum[M],cnt[M],a[N];
int blk,n,type,q,last,l,r,blkk;
int mul[N],mmul[N];
inline int gn(int a=0,char c=0){
for(;c<'0'||c>'9';c=getchar());
for(;c>47&&c<58;c=getchar())a=a*10+c-'0';
return a;}
char swt[20];
inline void change(int l,int r){
//为了压常数不择手段, 能不取模就不取模...
register int i;
if(bl[l]==bl[r]){
for(i=l;i<=r;++i){
a[i]+=mul[i-l+1]; if(a[i]>=p) a[i]-=p;
sum[bl[i]]+=mul[i-l+1];
if(sum[bl[i]]>=p) sum[bl[i]]-=p;
}
return;
}
int nxl=bl[l]*blk+1,endb=bl[r]-1,bll=nxl;
for(i=l;i<nxl;++i){
a[i]+=mul[i-l+1]; if(a[i]>=p) a[i]-=p;
sum[bl[i]]+=mul[i-l+1];
if(sum[bl[i]]>=p) sum[bl[i]]-=p;
}
for(i=(bl[r]-1)*blk+1;i<=r;++i){
a[i]+=mul[i-l+1]; if(a[i]>=p) a[i]-=p;
sum[bl[i]]+=mul[i-l+1];
if(sum[bl[i]]>=p) sum[bl[i]]-=p;
}
for(i=bl[l]+1;i<=endb;++i,bll+=blk){
frt[i]+=mul[bll-l+1]; if(frt[i]>=p) frt[i]-=p;
++cnt[i]; adum[i]+=bll-l+1;
if(adum[i]>=p) adum[i]-=p;
}
}
inline int query(int l,int r,int ans=0){
register int i;
int nxl=bl[l]*blk+1,bll=nxl-blk,brl,endb=bl[r]-1;
if(bl[l]==bl[r]){
for(i=l;i<=r;++i){
ans+=a[i]; if(ans>=p) ans-=p;
ans+=frt[bl[l]]; if(ans>=p) ans-=p;
ans+=1LL*(i-bll)*adum[bl[l]]%p; if(ans>=p) ans-=p;
ans+=1LL*mul[i-bll]*cnt[bl[l]]%p; if(ans>=p) ans-=p;
}
return ans;
}
for(i=l;i<nxl;++i){
ans+=a[i]; if(ans>=p) ans-=p;
ans+=frt[bl[l]]; if(ans>=p) ans-=p;
ans+=1LL*(i-bll)*adum[bl[l]]%p; if(ans>=p) ans-=p;
ans+=1LL*mul[i-bll]*cnt[bl[l]]%p; if(ans>=p) ans-=p;
}
for(i=(bl[r]-1)*blk+1,brl=i;i<=r;++i){
ans+=a[i]; if(ans>=p) ans-=p;
ans+=frt[bl[r]]; if(ans>=p) ans-=p;
ans+=1LL*(i-brl)*adum[bl[r]]%p; if(ans>=p) ans-=p;
ans+=1LL*mul[i-brl]*cnt[bl[r]]%p; if(ans>=p) ans-=p;
}
for(i=bl[l]+1;i<=endb;++i){
ans+=sum[i]; if(ans>=p) ans-=p;
ans+=1LL*frt[i]*blk%p; if(ans>=p) ans-=p;
ans+=1LL*mul[blk-1]*adum[i]%p; if(ans>=p) ans-=p;
ans+=1LL*mmul[blk-1]*cnt[i]%p; if(ans>=p) ans-=p;
}
return ans;
}
int main(){
freopen("stillmiss.in","r",stdin);
freopen("stillmiss.out","w",stdout);
n=gn(); type=gn(); q=gn();
blk=sqrt(n)+1;
register int i;
for(i=1;i<=n;++i){
bl[i]=(i-1)/blk+1;
mul[i]=1LL*i*(i+1)/2%p;
mmul[i]=mmul[i-1]+mul[i];
if(mmul[i]>=p) mmul[i]-=p;
}
while(q--){
scanf("%s",swt);
l=gn(),r=gn();
if(type){
l=(l+last-1)%n+1,r=(r+last-1)%n+1;
if(l>r) std::swap(l,r);
}
if(swt[0]=='Q'){
last=query(l,r);
printf("%d\n",last);
}
else change(l,r);
}
}
最后这道2s的题我倒是随便就艹过去了..
而且好像除了最后一组都是1~n这种卡分块的数据跑了1.014s, 前几个大数据点比std的\(O(nlogn)\)要快33%...
舒老师表示不开心... 可这跟我有什么关系吗?
分块大法好啊~