【学术篇】规律选手再次证明自己(舒老师的胡策题 T2 LX还在迷路)

只要你不强制在线, 我就能分块.
——Reflash

就算你强制在线, 我还是要分块.
——Enzymii

今天做了一波舒老师的毒瘤题, T1据说很水但是没思路所以直接放掉了..
去看了看T2好像可以水不错的分数, 那就去做一下.

我们先把题目放一下: (画风很迷很迷 大家稍微忍一下 忍一下)



就很显然一道数据结构题... 本来想写线段树, 但是标记看上去好像并不怎么好维护, 看一下数据范围, \(n\leq100000\)? 那我们可以分块啊~
题目的输入格式中有一行小字说的是: "想用离线分块水过去? 不存在的" 然后成功被窝打脸...但其实用的是在线分块...

那么具体要怎么写呢? 我们看一下这些操作...
这里默认看下去的都知道基础的分块怎么写了哈~


我们先来看这个奇怪的修改操作.
我们接下来会各种用到

\[\sum_{i=1}^xi=\frac{x(x+1)}2 \]

所以我们做一个宏定义就是偷个懒, 用一个\(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好了)

总结一下, 我们对每个完整的块维护四个标记:

  1. 块的首项增加的数
  2. 块左端点在当前操作区间的编号
  3. 块被操作区间完整包含的次数
  4. 还有一个最普通的, 不含特殊标记的区间和

当我们进行一次修改操作时, 对于不完整的块, 直接暴力修改单点值, 顺带维护标记4.
而对于每个完整的块, 设左端点在这个区间的编号为\(x\)的话:

  1. 标记1 +s(x)
  2. 标记2 +x
  3. 标记3 +1
    这样就维护完了.

我们再来看查询操作.
查询的时候, 对于不完整的块依然是暴力加和..
那么如何通过标记计算一个单点的值?

我们可以知道, 在这个点所在的块上, 维护着一堆标记来记录这个点增加了多少.
所以这个单点的值=基础值(就是暴力修改的时候改的值)+通过标记计算出的增加值.

我们通过上面的分析知道, 这个块的首项增加了[1]([x]表示标记x的值), 而这个块里的后一项比前一项要多[2]+[3]×(块内编号-1)..
综上可以得出

\[now_i=a_i+([1]+[2]*(i-l)+[3]*s(i-l) \]

其中\(now\)表示最终结果, \(a\)表示基础值, \(l\)表示区间的左端点. 这样算是\(O(1)\)的, 就不会影响复杂度.

那么完整的块怎么算呢?
很像.. 我们只要对每一项都求和就行了.

\[now_{bl}=sum_{bl}+[1]*blk+[2]*s(blk)+[3]*\sum_{i=1}^{blk-1}s(i) \]

其中后面的那一堆我们可以\(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%...

舒老师表示不开心... 可这跟我有什么关系吗?

分块大法好啊~

posted @ 2018-03-11 21:55  Enzymii  阅读(182)  评论(0编辑  收藏  举报