balabala---莫队

注意注意注意,名字我乱取的,可能有原题,不知道原题是什么

有趣等级:还好

有趣理由:完美利用莫队特性,堪称暴力出奇迹的典范(人家明明很正经的好吧),求幂小技巧

我觉得我写的题解循序渐进,引人深思!


Description

给出一个序列,每次给出一个询问(l,r,Mod),求区间[l,r]中所有连续子序列的和%Mod的值

Solution

题意简洁明了

考虑莫队,由于Mod的不确定,在进行维护指针的时候不能直接计算,应该在指针移动结束后计算答案。

直接求不好搞,很自然的想到考虑每个数的贡献

假设数x,在区间[l,r]中出现了c次

那么x对答案的贡献就是 x ∗ ( 2 r − l + 1 − c ∗ ( 2 c ) − 1 ) x\ast(2^{r-l+1-c}\ast(2^c)-1) x(2rl+1c(2c)1)

如果能知道区间内每个数值的出现次数就可以求出答案啦,但是数值可能有很多种,这貌似不能快速求出

再考虑对于出现次数相同的数,可以合并求答案,并且一个区间内不同的出现次数不会超过 长 度 \sqrt 长度

如果我们能够维护区间内不同的出现次数,那就可以求出答案啦

假如不管是否有重复,只要是过程中涉及到的出现次数都存下来。

当每次要计算答案以后,我们可以将之前存的出现次数去重(并且去掉以及不存在的),就只剩下不超过 n \sqrt n n 个了,然后就可以快速计算答案。故每次去重是 n + x \sqrt n+x n +x的, n \sqrt n n 是上个询问的残留,x是这次询问中涉及到的。考虑x的数量,x的数量事实上就是对于处理这个询问时指针的移动次数,而我们知道每一次指针的移动次数不会超过n,并且总移动次数不会超过 n n n\sqrt n nn ,所以去重总复杂度不超过 q n + n n q\sqrt n+n\sqrt n qn +nn 所以这个看似暴力的东西其实是可行的。

再讲一下如何计算答案

要统计一下出现次数为x的数的和,然后对于不超过 n \sqrt n n 个不同的出现次数,每个都参照上面所说的公式求答案。但是就2的幂如果用快速幂的话,时间复杂度就要多乘一个log,大概率过不了。

发现求的2的幂的次数都不超过区间长度len。先求出 2 2 2 ~ 2 l e n 2^{\sqrt len} 2l en,再求出 2 2 ∗ l e n 2^{2\ast \sqrt len} 22l en~ 2 l e n ∗ l e n 2^{\sqrt len \ast \sqrt len} 2l enl en,相当于对指数分块吧

然后除一除,取一下模,乘一下就好了

详细还是看代码吧(感觉我是抄std的。。。std跑的飞快,我跑的飞慢)

#include <bits/stdc++.h>
using namespace std;
int n,m,a[100010],c[100010],g[200010],Size,t=0,v,len,q;
long long ans[100010],p[100010],p1[100010];
bool use[200010];
long long s[100010];
struct dsa
{
	int l,r,id;
	long long Mod;
}qu[100010];
bool cmp(dsa p1,dsa p2)
{
	if (p1.l/Size!=p2.l/Size) 
	  return p1.l/Size<p2.l/Size;
	if (p1.l/Size&1) return p1.r<p2.r;
	return p2.r>p1.r;
}
void Insert(int x)
{
	s[ c[ a[x] ] ++ ] -= a[x],s[ g[ ++t ] = c[ a[x] ] ] += a[x];
}
void Remove(int x)
{
	s[ c[ a[x] ] -- ] -= a[x],s[ g[ ++t ] = c[ a[x] ] ] += a[x];
}
void init_power(int le,long long Mo)
{
	v=sqrt(le)+1;
	p[0]=1;
	for (int i=1;i<=v;i++)
	  p[i]=p[i-1]*2%Mo;
	p1[0]=1;
	for (int i=1;i*v<=le;i++)
	 p1[i]=p1[i-1]*p[v]%Mo;
}
long long power(int le,long long Mo)
{
	return p[le%v]*p1[le/v]%Mo;
}
long long calc(long long Mod)
{
	int tt=0;
	for (int i=1;i<=t;i++)
	  if ( g[i] && s[ g[i] ] && !use[ g[i] ])
	    use[ g[i] ]=true,g[ ++tt] = g[i];
	init_power(len,Mod);
	long long temp=power(len,Mod);
	t=tt;
	long long sum=0;
	for (int i=1;i<=t;i++)
    {
    	use[ g[i] ]=0;
    	sum=(sum+s[g[i]]%Mod*temp%Mod-s[g[i]]%Mod*power(len-g[i],Mod)%Mod)%Mod;
    }
    sum=(sum+Mod)%Mod;
    return sum;
}
int main()
{
	freopen("lorem.in","r",stdin);
	freopen("lorem.out","w",stdout); 
	scanf("%d%d",&n,&q);
	for (int i=1;i<=n;i++)
	  scanf("%d",&a[i]);
	for (int i=1;i<=q;i++)
	  scanf("%d%d%lld",&qu[i].l,&qu[i].r,&qu[i].Mod),qu[i].id=i;
	Size=sqrt(n);
	sort(qu+1,qu+q+1,cmp);
	int ll=1,rr=1;
	Insert(1);
	for (int i=1;i<=q;i++)
	{
		while (ll>qu[i].l) Insert(--ll);
		while (rr<qu[i].r) Insert(++rr);
		while (ll<qu[i].l) Remove(ll++);
		while (rr>qu[i].r) Remove(rr--);
		len=qu[i].r-qu[i].l+1,ans[qu[i].id]=calc(qu[i].Mod);
    }
    for (int i=1;i<=q;i++)
      printf("%lld\n",ans[i]);
    return 0;
}

对于求不同的出现次数,也可以分块做,出现次数大于 n \sqrt n n 的数不超过 n \sqrt n n 个,然后处理一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值