标签:扫描 line 暴力 位置 假设 lin count ++ 查询
在写序列莫队的时候,有时候我们会遇到这类问题:
为了统计答案,我们需要维护额外的结构或信息,导致时间复杂度从 \(O(n\sqrt{n})\) 变成了 \(O(nk\sqrt{n})\) 。
(这里我们假设序列长度 \(n\) 和 \(m\) 同阶,否则需要重新考虑块的大小)
如果这里的信息具有可差分性,我们就可以尝试使用莫队二次离线解决这个问题。
考虑我们现在已经统计的区间是 \([L,R]\) 。每次我们可以将一个新的位置 \(x\) 加入到 \([L,R]\) 里面来。
设 \(f([L,R],x)\) 为区间 \([L,R]\) 加入了 \(x\) 的新增贡献。由于我们知道贡献可差分,于是就有:
平时我们是直接在线解决这个贡献;现在我们考虑离线。
然后你发现:
假如我们顺序扫描 \([1,1],[1,2],...,[1,n]\) 这些前缀,并且维护它们的信息。于是我们就相当于有 \(O(n)\) 次修改操作和 \(O(n\sqrt{n})\) 次查询。
注意到修改和查询很不平均,因此,如果我们适当地放大修改的时间,而缩小查询的时间,我们就可以减小时间复杂度!
另附一些优化:
先思考暴力莫队该怎么做。
首先发现,当 \(k\) 确定的时候,可用的数其实不多(存在上界 \(P=\binom{14}{7} = 3432\) )。
因此我们可以先把这些符合要求的数暴力找出来。
然后,由于异或满足:\(a\oplus b=c\Leftrightarrow a\oplus c=b\) ,因此我们可以用桶存下来每个数的出现次数。对于 \(x\) 查询的时候,我们就枚举合法异或值,并倒推出对应的数是哪一个。
此时的时间是 \(O(Pn\sqrt{n})\) 。同时我们的维护的信息是 \(O(1)\) 修改、 \(O(P)\) 查询。
现在我们利用二次离线的技巧。此时我们需要降低查询时间。
显然我们可以直接倒过来做:我们加入 \(x\) 的时候,枚举合法异或值 \(k\) ;这就说明 \(x\oplus k\) 又多了一个可配对的数,因此我们要增加 \(x\oplus k\) 的答案。查询直接 \(O(1)\) 查。此时我们就做到了 \(O(P)\) 修改, \(O(1)\) 查询。
结合二次离线,时间就变成了 \(O(np+n\sqrt{n})\) 。
代码:
#include <cmath>
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int MAXN = 1e5 + 5, MAXS = 4e5 + 5, MAXK = 17005;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > ‘9‘ || s < ‘0‘ ){if( s == ‘-‘ ) f = -1; s = getchar();}
while( s >= ‘0‘ && s <= ‘9‘ ){x = ( x << 3 ) + ( x << 1 ) + ( s - ‘0‘ ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( ‘-‘ ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + ‘0‘ );
}
int T;
struct Query
{
LL ans;
int l, r, blk, id;
Query() { ans = l = r = blk = id = 0; }
Query( int L, int R, int ID ) { ans = 0, l = L, r = R, blk = l / T, id = ID; }
bool operator < ( const Query &b ) const { return blk == b.blk ? ( blk & 1 ? r < b.r : r > b.r ) : l < b.l; }
}Q[MAXN];
struct PSQuery
{
int l, r, f, id;
PSQuery() { l = r = 0, f = 1; }
PSQuery( int L, int R, int F, int ID ) { l = L, r = R, f = F, id = ID; }
}PSQ[MAXS];
int head[MAXN], nxt[MAXS];
int cnt;
int num[MAXN], tot;
LL pre[MAXN];
LL ans[MAXN];
int buc[MAXK];
int a[MAXN];
int N, M, K;
int Ask( const int x ) { return buc[x]; }
void Clr() { for( int i = 0 ; i < 16384 ; i ++ ) buc[i] = 0; }
void Insert( const int x ) { for( int i = 1 ; i <= tot ; i ++ ) buc[num[i] ^ x] ++; }
void Add( const int p, const int left, const int rig, const int f, const int id )
{
PSQ[++ cnt] = PSQuery( left, rig, f, id );
nxt[cnt] = head[p], head[p] = cnt;
}
int main()
{
read( N ), read( M ), read( K ), T = sqrt( N );
for( int i = 1 ; i <= N ; i ++ ) read( a[i] );
for( int i = 1, l, r ; i <= M ; i ++ ) read( l ), read( r ), Q[i] = Query( l, r, i );
std :: sort( Q + 1, Q + 1 + M );
for( int i = 0 ; i < 16384 ; i ++ )
if( __builtin_popcount( i ) == K )
num[++ tot] = i;
for( int i = 1 ; i <= N ; i ++ ) pre[i] = Ask( a[i] ) + pre[i - 1], Insert( a[i] );
int L = 1, R = 0, qL, qR;
for( int i = 1 ; i <= M ; i ++ )
{
qL = Q[i].l, qR = Q[i].r;
if( L < qL ) Add( R, L, qL - 1, -1, i ), Q[i].ans += pre[qL - 1] - pre[L - 1], L = qL;
if( qL < L ) Add( R, qL, L - 1, +1, i ), Q[i].ans -= pre[L - 1] - pre[qL - 1], L = qL;
if( R < qR ) Add( L - 1, R + 1, qR, -1, i ), Q[i].ans += pre[qR] - pre[R], R = qR;
if( qR < R ) Add( L - 1, qR + 1, R, +1, i ), Q[i].ans -= pre[R] - pre[qR], R = qR;
}
Clr();
for( int i = 1 ; i <= N ; i ++ )
{
Insert( a[i] );
for( int j = head[i] ; j ; j = nxt[j] )
for( int k = PSQ[j].l, val ; k <= PSQ[j].r ; k ++ )
{
val = Ask( a[k] );
if( k <= i && K == 0 ) val --;
Q[PSQ[j].id].ans += PSQ[j].f * val;
}
}
for( int i = 1 ; i <= M ; i ++ ) ans[Q[i].id] = ( Q[i].ans += Q[i - 1].ans );
for( int i = 1 ; i <= M ; i ++ ) write( ans[i] ), putchar( ‘\n‘ );
return 0;
}
标签:扫描 line 暴力 位置 假设 lin count ++ 查询
原文地址:https://www.cnblogs.com/crashed/p/13562320.html