SHOI2016 隨機序列 (線段樹)

2022-04-30 18:24:12 字數 2839 閱讀 1876

你的面前有\(n\)個數排成一行,分別為\(a_,a_,a_......,a_\)。你打算在每相鄰的兩個\(a_\)和\(a_\)間都插入乙個加號、減號或者乘號。那麼一共有\(3^\)種可能的表示式。 你對所有可能的表示式的值的和非常感興趣。但這畢竟太簡單了,所以你還打算支援乙個修改操作,可以修改某個\(a_\)的值。 你能夠編寫乙個程式對每個修改都輸出修改完之後所有可能表示式的和嗎?注意,修改是永久的,也就是說每次修改都是在上一次修改的基礎上進行,而不是在最初的表示式上進行。

第一行包含兩個正整數\(n\)和\(q\),為數的個數和詢問的個數。

第二行包含\(n\)個非負整數,依次表示\(a_,a_,a_......,a_\)。

接下來\(q\)行,每行包含兩個非負整數\(t\)和\(v\),表示要將\(a_\)修改為\(v\),其中\(1<=t<=n\)。

保證對於\(1<=j<=n,1<=i<=q\),都有\(a_,v_<=10^4\) 。

輸出\(q\)行。對於每個修改輸出一行,包含乙個整數,表示修改之後所有可能表示式的和,對\(10^9+7\)取模。

5 59384 887 2778 6916 7794

2 8336

5 493

3 1422

1 28

4 60

890543652

252923708

942282590

228728040

608998099

對於\(20%\)的資料,\(n,q<=20\)

對於\(50%\)的資料,\(n,q<=1000\)

對於\(100%\)的資料,\(n,q<=100000\)

首先我們可以考慮,對於乙個\(n=5\)情況下

肯定會有\(\begina_*a_+a_*a_-a_\\a_*a_-a_*a_+a_\\\end\)

這樣一對相反的式子,其中求和之後就只剩下\(a_*a_\)這種形式的字首積

對於每個式子,都肯定會有與他相反的式子,於是最後只用求所有的字首積的和就好了

但是怎麼求字首積的和呢

我們首先定義\(a(1)\)後面的符號位為第一符號位,\(a(2)\)後面的為第二符號位,以此類推

可以得到一共有4個符號位,其中沒有第五符號位

我們令\(g(i)=\prod\limits_^ia_j\),也就是從\(1\)到\(i\)的\(a_\)的乘積。

設\(f(i)\)為\(g(i)\)出現的個數

可以發現\(g(5)=a1*a2*a3*a4*a5\),這種情況下,\(f(5)=1\)

那麼,\(g(4)\)的情況就是由\(g(5)\)中的第四符號位變成減號或加號所組成:

\(\begina_*a_*a_*a_-a_\\a_*a_*a_*a_+a_\\\end\)

所以\(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)=\begin1&x=1\\2&x=2\\f(x+1)\times3&x>2\end\)

所以,我們可以輕易\(o(n)\)計算出\(g(i)\)和\(f(i)\)

接著建一棵線段樹,每個節點維護\(\sum\limits_^g(i)*f(i)\)的值

最後詢問時輸出根節點所存的值就好了

接下來我們看看修改操作怎麼實現

比如,還是那五個數,將其中的\(a_\)變成\(k\)

原式\(=f(1)*a_+f(2)*a_*a_+f(3)*a_*a_*a_+f(4)*a_*a_*a_*a_+f(5)*a_*a_*a_*a_*a_\)

變成 \(f(1)*a_+f(2)*a_*a_+f(3)*a_*a_*k+f(4)*a_*a_*k*a_+f(5)*a_*a_*k*a_*a_\)

可以看到,受到影響的只有\(g(3),g(4),g(5)\)

於是,我們對於 \(3<=i<=5\) 的\(g(i)\)進行區間修改,每個乘上\(\frac}\)就好了,在這裡因為除法+取模,於是我們選擇乘法逆元(大佬的部落格,不是我的)

#includeusing 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

t[n<<2];

void up(int k)

void build(int k,int l,int r)

int mid=(l+r)>>1;

build(k<<1,l,mid);

build(k<<1|1,mid+1,r);

up(k);

}void pushdown(int k)

}void modify(int k,int l,int r,int x,int y,ll d)

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()

build(1,1,n);

int p,v;

for(int i=1;i<=q;i++)

return 0;

}

SHOI2016 隨機序列

給你乙個數列,在相鄰兩個數之間插入加號,減號或乘號 每次支援單點修改,求所有這樣可以得到的表示式之和,膜1e9 7 sol 我是個 sb 可以發現,如果某位置出現了加號,後面一定有乙個減號把它消掉,於是答案就是一些出現了好幾次的字首積之和 算一下每段字首積的貢獻即可 include define i...

BZOJ 4597 Shoi2016 隨機序列

很可做的一道題 考慮一下一段序列的答案為b x,x為最右連續一段是乘積,然後和為x,b則為對應的左邊的和 然後考慮在右邊加乙個數k,分類討論一下發現x kx,b 2x 3b 於是就可以dp了 然後dp可以矩陣轉移 於是就可以用線段樹維護矩陣 就水過去了 好像很簡單哎,ac率好高的說 include ...

BZOJ4597 Shoi2016 隨機序列

bzoj4597 一開始看題一臉不可做的樣子。肯定又有什麼鬼畜的結論qa q 看看題解之後 和加減沒什麼關係。維護一下字首積的和就好了。qa q 然後自己拿n 3 手寫試了一發。md為什麼不自己想。an s i 1n 1 2 3n i 1 j 1i ai j 1 nai 線段樹維護一下就好了嘛。然後...