題解 P5072 【[Ynoi2015]盼君勿忘】
題意簡述
不看前面的廢話就是題意簡述。
題解
這是資料結構一百題的第50題(一半了哦)的紀念題解。
無修改操作,基本確定大方向莫隊。
考慮查詢的問題,我們可以轉化一下。即求區間內每個數出現的次數。
一個區間 \([l,r]\) 的子序列數量為:
\[\sum_{i=0}^{r-l+1}C^{i}_{r-l+1}=2^{r-l+1} \]比如一個數在區間 \([l,r]\) 出現了 \(k\) 次,那麼一共有 \(2^{r-l+1-k}\) 個子序列不包含這個數。這個很好理解,從組合數的意義可知。那麼就有 \(2^{r-l+1}-2^{r-l+1-k}\) 個子序列包含了這個數。
那麼我們就可以用莫隊維護區間中出現了 \(k\)
問題又來了:每次詢問的模數是不確定的,我們需要每次都需要 \(\Theta(n)\) 處理一遍2的冪。
有沒有什麼方法能把處理這個東西的複雜度降到 \(\Theta(\sqrt{n})\) 或以下呢?
對此SyadouHayami表示我們可以打個高精。
方法是有的。
我們可以每次詢問都處理出 \(2^{1},2^{2},\cdots,2^{\sqrt{n}}\) ,以及 \(2^{2\sqrt{n}},2^{3\sqrt{n}},\cdots,2^{n}\),都只需要 \(\Theta(\sqrt{n})\)
pow1
和pow2
。
那麼 \(2^{x}\operatorname{mod}p=(pow1_{x\operatorname{mod}\sqrt{n}}\times pow2_{\lfloor x\div\sqrt{n}\rfloor})\operatorname{mod}p\)。
於是就解決問題了。
我的程式碼遇到了一下兩個玄學問題,貼出來給同樣情況的人看看:
-
連結串列部分的
prev
和next
如果放在結構體裡會T。 -
pow1
,pow2
,sum
,cnt
幾個陣列的定義如果放在最開頭和isa
以及ans
兩個陣列一起會RE。
#include <cstdio> #include <iostream> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int Maxn = 1e5 + 10; const int Size = 320; int n, m, isa[ Maxn ], ans[ Maxn ]; struct Query_Node { int l, r, p, id, pos; } Q[ Maxn ]; struct Linked_List { int tot, prev[ Maxn ], next[ Maxn ]; Linked_List( ) { tot = 0; } void insert( int x ) { next[ tot ] = x; prev[ x ] = tot; tot = x; } void erase( int x ) { if( tot == x ) tot = prev[ x ]; else { next[ prev[ x ] ] = next[ x ]; prev[ next[ x ] ] = prev[ x ]; } prev[ x ] = next[ x ] = 0; } } llt; bool cmp( Query_Node rhs, Query_Node shr ) { if( rhs.pos != shr.pos ) return rhs.l < shr.l; else return rhs.r < shr.r; } int pow1[ Maxn ], pow2[ Maxn ]; void Pare_Two( int p ) { pow1[ 0 ] = pow2[ 0 ] = 1; for( int i = 1; i <= Size; ++ i ) pow1[ i ] = 1ll * 2 * pow1[ i - 1 ] % p; for( int i = 1; i <= Size; ++ i ) pow2[ i ] = 1ll * pow1[ Size ] * pow2[ i - 1 ] % p; } int Get_Two( int x, int p ) { return 1ll * pow1[ x % Size ] * pow2[ x / Size ] % p; } int sum[ Maxn ], cnt[ Maxn ]; void Make_Cont( int x, int f ) { int pos = isa[ x ]; sum[ cnt[ pos ] ] -= pos; if ( ! sum[ cnt[ pos ] ] ) llt.erase( cnt[ pos ] ); if( f == 1 ) ++cnt[ pos ]; else --cnt[ pos ]; if ( ! sum[ cnt[ pos ] ] ) llt.insert( cnt[ pos ] ); sum[ cnt[ pos ] ] += pos; } void Contribute( ) { int l = 1, r = 0; for( int i = 1; i <= m; ++ i ) { Pare_Two( Q[ i ].p ); while( l > Q[ i ].l ) Make_Cont( --l, 1 ); while( l < Q[ i ].l ) Make_Cont( l++, 0 ); while( r > Q[ i ].r ) Make_Cont( r--, 0 ); while( r < Q[ i ].r ) Make_Cont( ++r, 1 ); for( int s = llt.next[ 0 ]; s; s = llt.next[ s ] ) { int val = 1ll * sum[ s ] * ( Get_Two( r - l + 1, Q[ i ].p ) - Get_Two( r - l + 1 - s, Q[ i ].p ) + Q[ i ].p ) % Q[ i ].p; ans[ Q[ i ].id ] += val; ans[ Q[ i ].id ] %= Q[ i ].p; } } } signed main( ) { scanf( "%d %d", &n, &m ); for( int i = 1; i <= n; ++ i ) scanf( "%d", &isa[ i ] ); for( int i = 1; i <= m; ++ i ) { int l, r, p; scanf( "%d %d %d", &l, &r, &p ); Q[ i ].l = l, Q[ i ].r = r; Q[ i ].p = p, Q[ i ].id = i; Q[ i ].pos = l / Size; } sort( Q + 1, Q + 1 + m, cmp ); Contribute( ); for( int i = 1; i <= m; ++ i ) printf( "%d\n", ans[ i ] ); return 0; }