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


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

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

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

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



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

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


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

    [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(qsqrt 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
    ",last);
    		}
    		else change(l,r);
    	}
    }
    

    最后这道2s的题我倒是随便就艹过去了..
    而且好像除了最后一组都是1~n这种卡分块的数据跑了1.014s, 前几个大数据点比std的(O(nlogn))要快33%...

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

    分块大法好啊~

  • 相关阅读:
    uboot串口与标准输入输出代码详解
    uboot打开Debug
    git添加公钥后报错sign_and_send_pubkey: signing failed: agent refused operation的解决办法
    git 代码管理工具,很不错,值得推荐
    Ubuntu 压缩解压命令
    OMAPL138调试笔记
    网络
    关于运放
    win7 linux 双系统删除linux & 双系统安装
    dedecms 蜘蛛抓取设置 robots.txt
  • 原文地址:https://www.cnblogs.com/enzymii/p/8545677.html
Copyright © 2020-2023  润新知