「SHOI2016」随机序列](线段树)
\(Description\)
你的面前有\(n\)个数排成一行,分别为\(a_{1},a_{2},a_{3}......,a_{n}\)。你打算在每相邻的两个\(a_{i}\)和\(a_{i+1}\)间都插入一个加号、减号或者乘号。那么一共有\(3^{n-1}\)种可能的表达式。 你对所有可能的表达式的值的和非常感兴趣。但这毕竟太简单了,所以你还打算支持一个修改操作,可以修改某个\(a_{i}\)的值。 你能够编写一个程序对每个修改都输出修改完之后所有可能表达式的和吗?注意,修改是永久的,也就是说每次修改都是在上一次修改的基础上进行,而不是在最初的表达式上进行。
\(Input\)
第一行包含两个正整数\(n\)和\(q\),为数的个数和询问的个数。
第二行包含\(n\)个非负整数,依次表示\(a_{1},a_{2},a_{3}......,a_{n}\)。
接下来\(q\)行,每行包含两个非负整数\(t\)和\(v\),表示要将\(a_{t}\)修改为\(v\),其中\(1<=t<=n\)。
保证对于\(1<=j<=n,1<=i<=q\),都有\(a_{j},v_{i}<=10^4\) 。
\(Output\)
输出\(q\)行。对于每个修改输出一行,包含一个整数,表示修改之后所有可能表达式的和,对\(10^9+7\)取模。
\(Sample\) \(Input\)
5 5
9384 887 2778 6916 7794
2 8336
5 493
3 1422
1 28
4 60
\(Sample\) \(Output\)
890543652
252923708
942282590
228728040
608998099
\(Hint\)
对于\(20%\)的数据,\(n,q<=20\)
对于\(50%\)的数据,\(n,q<=1000\)
对于\(100%\)的数据,\(n,q<=100000\)
思路
首先我们可以考虑,对于一个\(n=5\)情况下
肯定会有\(\begin{cases}a_{1}*a_{2}+a_{3}*a_{4}-a_{5}\\a_{1}*a_{2}-a_{3}*a_{4}+a_{5}\\\end{cases}\)
这样一对相反的式子,其中求和之后就只剩下\(a_{1}*a_{2}\)这种形式的前缀积
对于每个式子,都肯定会有与他相反的式子,于是最后只用求所有的前缀积的和就好了
但是怎么求前缀积的和呢
我们首先定义\(a(1)\)后面的符号位为第一符号位,\(a(2)\)后面的为第二符号位,以此类推
可以得到一共有4个符号位,其中没有第五符号位
我们令\(g(i)=\prod\limits_{j=1}^ia_j\),也就是从\(1\)到\(i\)的\(a_{i}\)的乘积。
设\(f(i)\)为\(g(i)\)出现的个数
可以发现\(g(5)=a1*a2*a3*a4*a5\),这种情况下,\(f(5)=1\)
那么,\(g(4)\)的情况就是由\(g(5)\)中的第四符号位变成减号或加号所组成:
\(\begin{cases}a_{1}*a_{2}*a_{3}*a_{4}-a_{5}\\a_{1}*a_{2}*a_{3}*a_{4}+a_{5}\\\end{cases}\)
所以\(f(4)=2\)
同理,\(g(3)\)就是由\(g(4)\)中的第三符号位变成加号或减号,第四符号位变成加号、减号、乘号
所以\(f(3)=6\),即 \(2\times3=6\)
易得,\(f(2)=2\times3\times3=18\),\(f(1)=2\times3\times3\times3=54\)
所以我们可以得到关于\(f(x)\)的通项:
\(f(x)=\begin{cases}1&x=1\\2&x=2\\f(x+1)\times3&x>2\end{cases}\)
所以,我们可以轻易\(O(n)\)计算出\(g(i)\)和\(f(i)\)
接着建一棵线段树,每个节点维护\(\sum\limits_{i=l}^{r}g(i)*f(i)\)的值
最后询问时输出根节点所存的值就好了
接下来我们看看修改操作怎么实现
比如,还是那五个数,将其中的\(a_{3}\)变成\(k\)
原式\(=f(1)*a_{1}+f(2)*a_{1}*a_{2}+f(3)*a_{1}*a_{2}*a_{3}+f(4)*a_{1}*a_{2}*a_{3}*a_{4}+f(5)*a_{1}*a_{2}*a_{3}*a_{4}*a_{5}\)
变成 \(f(1)*a_{1}+f(2)*a_{1}*a_{2}+f(3)*a_{1}*a_{2}*k+f(4)*a_{1}*a_{2}*k*a_{4}+f(5)*a_{1}*a_{2}*k*a_{4}*a_{5}\)
可以看到,受到影响的只有\(g(3),g(4),g(5)\)
于是,我们对于 \(3<=i<=5\) 的\(g(i)\)进行区间修改,每个乘上\(\frac{k}{a_{3}}\)就好了,在这里因为除法+取模,于是我们选择乘法逆元(大佬的博客,不是我的)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=10010,mod=1e9+7;
typedef long long ll;
int n,q;
ll inv[M],g[N],f[N];
int a[N];
struct tree
{
long long val,tag;
}t[N<<2];
void up(int k)
{
t[k].val=(t[k<<1].val+t[k<<1|1].val)%mod;
}
void build(int k,int l,int r)
{
t[k].tag=1;
if(l==r)
{
t[k].val=g[l]*f[l]%mod;
return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
up(k);
}
void pushdown(int k)
{
if(t[k].tag!=1)
{
t[k<<1].val=(t[k<<1].val*t[k].tag)%mod;
t[k<<1].tag=(t[k<<1].tag*t[k].tag)%mod;
t[k<<1|1].val=(t[k<<1|1].val*t[k].tag)%mod;
t[k<<1|1].tag=(t[k<<1|1].tag*t[k].tag)%mod;
t[k].tag=1;
}
}
void modify(int k,int l,int r,int x,int y,ll d)
{
if(x<=l&&r<=y)
{
t[k].val=(t[k].val*d)%mod;
t[k].tag=(t[k].tag*d)%mod;
return ;
}
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid)modify(k<<1,l,mid,x,y,d);
if(y>mid)modify(k<<1|1,mid+1,r,x,y,d);
up(k);
}
int main()
{
scanf("%d %d",&n,&q);
inv[0]=0,inv[1]=1;
for(int i=2;i<=M;i++)inv[i]=inv[mod%i]*1ll*(mod-mod/i)%mod;
ll sum=1;
f[n]=1,f[n-1]=2;
for(int i=n-2;i>=1;i--)f[i]=(f[i+1]*3)%mod;
g[0]=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
g[i]=(g[i-1]*1ll*a[i])%mod;
}
build(1,1,n);
int p,v;
for(int i=1;i<=q;i++)
{
scanf("%d %d",&p,&v);
modify(1,1,n,p,n,1ll*v*inv[a[p]]%mod);
a[p]=v;
printf("%lld\n",t[1].val);
}
return 0;
}