GSS8 - Can you answer these queries VIII 题解

题目传送门

题目大意: 带插入、删除、修改,每次询问给出 l , r l,r l,r,求 ( ∑ i = l r A i × ( i − l + 1 ) k )   m o d   2 32 (\sum_{i=l}^r A_i \times (i-l+1)^k)\bmod 2^{32} (i=lrAi×(il+1)k)mod232

题解

调这道题的代码用了一天……轻轻的谴责一下这题的时限

假如这题没有了 k k k,那么就很好做了, s p l a y splay splay随手就能 A C AC AC,但是这个 k k k不好搞,但是依然考虑用 s p l a y splay splay来维护,对于每一个节点的答案,还是通过合并左右儿子的答案来得到。

为了方便,下面的柿子就不把那个取模加上去了,反正大家都懂~ 主要是因为懒得写

因为 k k k是会变化的,但是它的范围只有 1 ≤ k ≤ 10 1\leq k \leq 10 1k10,所以每个节点可以开一个 a n s ans ans数组,用来记录不同的 k k k的答案。

s p l a y splay splay上的每个节点都维护一个 l , r l,r l,r,表示管理的区间,那么有:

a n s k = ∑ i = l r a i × ( i − l + 1 ) k ans_k=\sum_{i=l}^r a_i \times (i-l+1)^k ansk=i=lrai×(il+1)k

设当前节点在原序列中的 s s s位置,让每个节点再维护一个 s i z e size size,表示自己这棵子树的大小,那么有:

a n s k = ( ∑ i = l s − 1 a i × ( i − l + 1 ) k ) + ( a s × ( s − l + 1 ) k ) + ( ∑ i = s + 1 r a i × ( i − l + 1 ) k ) = l e f t s o n   .   a n s k + a s × ( l e f t s o n   .   s i z e ) k + ( ∑ i = s + 1 r a i × ( i − l + 1 ) k ) ans_k=(\sum_{i=l}^{s-1} a_i \times (i-l+1)^k)+(a_s \times (s-l+1)^k)+(\sum_{i=s+1}^r a_i \times (i-l+1)^k)\\ =leftson~.~ans_k+a_s \times(leftson~.~size)^k+(\sum_{i=s+1}^r a_i \times (i-l+1)^k) ansk=(i=ls1ai×(il+1)k)+(as×(sl+1)k)+(i=s+1rai×(il+1)k)=leftson . ansk+as×(leftson . size)k+(i=s+1rai×(il+1)k)
那么左儿子的答案我们已经有了,所以 l e f t s o n   .   a n s k leftson~.~ans_k leftson . ansk可以 O ( 1 ) O(1) O(1)搞定,中间的 a s × ( l e f t s o n   .   s i z e ) k a_s \times(leftson~.~size)^k as×(leftson . size)k也是可以用一个快速幂解决掉的。问题只剩下了右边部分,直接求是不可能的,于是需要利用 r i g h t s o n   .   a n s rightson~.~ans rightson . ans来快速的求,而 r i g h t s o n   .   a n s k rightson~.~ans_k rightson . ansk是这个样子的:
∑ i = s + 1 r a i × ( i − s ) k \sum_{i=s+1}^r a_i \times (i-s)^k i=s+1rai×(is)k
和上面的柿子相比,问题在于上面的 ( i − l + 1 ) (i-l+1) (il+1)部分,这个部分和右儿子的 a n s ans ans的柿子内部分差别较大,于是考虑往里面加点东西,在 ( i − l + 1 ) (i-l+1) (il+1)中提取出 i − s i-s is,于是变成了这样:
∑ i = s + 1 r a i × ( i − s + s − l + 1 ) k ∑ i = s + 1 r a i × ( ( i − s ) + ( s − l + 1 ) ) k \sum_{i=s+1}^r a_i \times (i-s+s-l+1)^k\\ \sum_{i=s+1}^r a_i \times ((i-s)+(s-l+1))^k i=s+1rai×(is+sl+1)ki=s+1rai×((is)+(sl+1))k
利用二项式定理,可以将这个柿子拆开:
∑ i = s + 1 r a i × ∑ j = 0 k C k j ( i − s ) j ( s − l + 1 ) k − j \sum_{i=s+1}^r a_i \times \sum_{j=0}^k C_k^j (i-s)^j(s-l+1)^{k-j} i=s+1rai×j=0kCkj(is)j(sl+1)kj
根据 ∑ \sum 的性质,可以转化成这个样子:
∑ j = 0 k C k j ( s − l + 1 ) k − j ∑ i = s + 1 r ( i − s ) j × a i \sum_{j=0}^k C_k^j (s-l+1)^{k-j} \sum_{i=s+1}^r (i-s)^j \times a_i j=0kCkj(sl+1)kji=s+1r(is)j×ai
此时右边正是我们想要的东西,于是最终的柿子长这样:
∑ j = 0 k C k j ( s − l + 1 ) k − j r i g h t s o n   .   a n s j \sum_{j=0}^k C_k^j (s-l+1)^{k-j} rightson~.~ans_j j=0kCkj(sl+1)kjrightson . ansj

因为 k k k很小,所以可以对 C k j C_k^j Ckj做个预处理,于是现在利用左右儿子的答案,可以做到 O ( k l o g k ) O(klogk) O(klogk)的转移,差不多相当于 O ( l o g n ) O(logn) O(logn),于是总的来说,得到了一个 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的做法。

但是这题很不人道,单旋的 s p l a y splay splay是过不去的(至少我在努力卡常加优化之后是没有改变TLE的局面),于是我最终被迫改成了双旋。

还要注意的是,这题的数组的下标是从0开始的。因为是对 2 32 2^{32} 232取模,所以变量用 u n s i g n e d   i n t unsigned~int unsigned int,让它自然溢出即可。

最终用了 22.9 s 22.9s 22.9s通过此题。

代码中大多都是 s p l a y splay splay的板子,重点看一下check函数即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#define ui unsigned int
int n,m;
ui a[100010],c[11][11],fac[11];
ui ksm(ui x,int y)
{
	ui re=1,tot=x;
	while(y>0)
	{
		if(y%2==1)re=re*tot;
		tot=tot*tot;
		y/=2;
	}
	return re;
}
struct node{
	ui ans[11],val;
	int size;
	node *zuo,*you,*fa;
	node(ui d,node *father)
	{
		for(int i=0;i<=10;i++)
		ans[i]=d;
		val=d;
		zuo=you=NULL;
		size=1;
		fa=father;
	}
	void check()
	{
		size=1;//别忘了处理size
		if(zuo!=NULL)size+=zuo->size;
		if(you!=NULL)size+=you->size;
		for(int i=0;i<=10;i++)
		{
			ans[i]=val*ksm((ui)(zuo!=NULL?zuo->size:0)+1,i)+(zuo!=NULL?zuo->ans[i]:0);//先加上自己的值以及左儿子的值
			for(int j=0;j<=i;j++)//加上右儿子的值
			ans[i]=ans[i]+ksm((ui)(zuo!=NULL?zuo->size:0)+1ll,i-j)*c[j][i]*(you!=NULL?you->ans[j]:0ll);
		}
	}
	node *findrank(int x)
	{
		if(zuo!=NULL&&x<=zuo->size)return zuo->findrank(x);
		if(zuo!=NULL)x-=zuo->size;
		if(x==1)return this;
		return you->findrank(x-1);
	}
};
node *root=NULL;
void rotate(node *x)
{
	node *fa=x->fa,*gfa=fa->fa;
	if(fa->zuo==x)
	{
		fa->zuo=x->you;
		if(x->you!=NULL)x->you->fa=fa;
		x->you=fa;
	}
	else
	{
		fa->you=x->zuo;
		if(x->zuo!=NULL)x->zuo->fa=fa;
		x->zuo=fa;
	}
	x->fa=gfa;fa->fa=x;
	if(gfa!=NULL)
	{
		if(gfa->zuo==fa)gfa->zuo=x;
		else gfa->you=x;
	}
	fa->check();
}
void splay(node *x,node *to)
{
	while(x->fa!=to)
	{
		if(x->fa->fa!=to&&(x->fa->you==x)==(x->fa->fa->you==x->fa))rotate(x->fa);
		rotate(x);
	}
	x->check();
	if(to==NULL)root=x;
}
void add(int x,ui y)
{
	if(root==NULL)
	{
		root=new node(y,NULL);
		return;
	}
	node *p=root->findrank(x);
	if(p->you==NULL)p->you=new node(y,p),splay(p->you,NULL);
	else
	{
		node *now=p->you;
		while(now->zuo!=NULL)now=now->zuo;
		now->zuo=new node(y,now);
		now=now->zuo;
		splay(now,NULL);
	}
}
void del(int x)
{
	node *p=root->findrank(x);
	splay(p,NULL);
	if(p->zuo==NULL&&p->you==NULL)root=NULL;
	else if(p->zuo==NULL&&p->you!=NULL)root=p->you,root->fa=NULL;
	else if(p->zuo!=NULL&&p->you==NULL)root=p->zuo,root->fa=NULL;
	else
	{
		node *pre=p->zuo;
		while(pre->you!=NULL)pre=pre->you;
		splay(pre,p);
		pre->you=p->you;
		p->you->fa=pre;
		pre->fa=NULL;
		root=pre;
	}
}
void change(int x,ui y)
{
	node *p=root->findrank(x);
	splay(p,NULL);
	p->val=y;
	p->check();
}
void ask(int l,int r,int k)
{
	node *x=root->findrank(l);
	node *y=root->findrank(r);
	splay(x,NULL);splay(y,x);
	printf("%u\n",root->you->zuo->ans[k]);
}
void work()//预处理组合数,c[j][i]事实上表示C(i,j)
{
	fac[0]=1;
	for(int i=1;i<=10;i++)
	fac[i]=fac[i-1]*(ui)i;
	for(int i=0;i<=10;i++)
	for(int j=0;j<=i;j++)
	c[j][i]=fac[i]/fac[j]/fac[i-j];
}

int main()
{
	scanf("%d",&n);
	work();
	add(0,0);//处理边界
	add(1,0);
	ui x;
	for(int i=1;i<=n;i++)
	{
		scanf("%u",&x);
		add(i,x);
	}
	scanf("%d",&m);
	char s[2];
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s);
		switch(s[0])
		{
			case 'I':
				{
					int pos;ui val;
					scanf("%d %u",&pos,&val);
					add(pos+1,val);
					break;
				}
			case 'D':
				{
					int pos;
					scanf("%d",&pos);
					del(pos+2);
					break;
				}
			case 'R':
				{
					int pos;ui val;
					scanf("%d %u",&pos,&val);
					change(pos+2,val);
					break;
				}
			case 'Q':
				{
					int l,r,k;
					scanf("%d %d %d",&l,&r,&k);
					ask(l+1,r+3,k);
					break;
				}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值