题目大意: 带插入、删除、修改,每次询问给出 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×(i−l+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 1≤k≤10,所以每个节点可以开一个 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=l∑rai×(i−l+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=l∑s−1ai×(i−l+1)k)+(as×(s−l+1)k)+(i=s+1∑rai×(i−l+1)k)=leftson . ansk+as×(leftson . size)k+(i=s+1∑rai×(i−l+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+1∑rai×(i−s)k
和上面的柿子相比,问题在于上面的
(
i
−
l
+
1
)
(i-l+1)
(i−l+1)部分,这个部分和右儿子的
a
n
s
ans
ans的柿子内部分差别较大,于是考虑往里面加点东西,在
(
i
−
l
+
1
)
(i-l+1)
(i−l+1)中提取出
i
−
s
i-s
i−s,于是变成了这样:
∑
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+1∑rai×(i−s+s−l+1)ki=s+1∑rai×((i−s)+(s−l+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+1∑rai×j=0∑kCkj(i−s)j(s−l+1)k−j
根据
∑
\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=0∑kCkj(s−l+1)k−ji=s+1∑r(i−s)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=0∑kCkj(s−l+1)k−jrightson . 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;
}
}
}
}