一、块状数组
块状数组就是将数组划分若干块(一般为sqrt(n)使复杂度最低),通过预处理i~j块的答案(复杂度可以保证在n*sqrt(n)内),
求答案时就根据预处理的答案和暴力枚举首尾多余个数得出最后答案,首尾多余的个数是不超过2*sqrt(n),这样总的复杂度
为((n+q)sqrt(n)),q为查询的次数。
二、区间众数
1.题目描述
有n个数x,查询q次,求【L,R】区间的众数个数(其中n,q<=20000,0<=L<=R
大部分一般做到的众数问题是可以暴力区间求答案的,但是这里数据过大无法暴力,我们需要使用块状数组解决这类问题(复杂度n*sqrt(n))。
为了使问题更一般性假设每个数0<=x
首先预处理i~j块的众数个数,如果求出i~j-1的众数个数了,现在要求i~j的众数个数,是不是就很简单了,众数个数要么是i~j-1的众数个数,要么就是第j块中数字出现的次数,那么就可以枚举第j块的数字,如果我们预处理出前i块数字j出现的次数(定义为sum[i][j]),那么第j块出现的数字a[p](其中p处于第j块中)在i~j块出现的次数为sum[j][ a[p] ]-sum[i-1][ a[p] ]。并且sum数字的预处理很简单。
预处理完成后,就是查询问题,查询其实和预处理的原理是一样的,假设L属于第x块,R属于第y块,那么此区间的众数个数要么是x+1~y-1块的众数个数,要么是L~第x块结束中数字出现的次数,要么就是第y块开始~R中数字出现的次数,由于x+1~y-1块的众数个数已经处理,那么只需要O(1)即可知道,另外两块就可以直接暴力统计个数就行(这里需要注意只需要初始化这两块中出现的数字,这样初始化复杂度只有sqrt(n),初值应为sum[y-1][p]-sum[x][p],p为块中出现的数字),另外注意处理L和R处于同一块的情况,这样区间众数问题就解决了。
#include
using namespace std;
const int maxn = 50005, N = 305;
int sum[N][maxn], a[maxn], mul[N][N], cc[N][N];
int tmp[maxn];
//sum[i][j]统计前i块数字j出现的次数
//mul[i][j]计算众数个数最多且数字最少的数
//cc[i][j]计算众数最多个数
void solve ( )
{
int n, Q;
//freopen ( "in0.in", "r", stdin );
//freopen ( "in1.out", "w", stdout );
scanf ( "%d%d", &n, &Q );
for ( int i = 0; i < n; i ++ )
scanf ( "%d", &a[i] );
int blo = ( int )sqrt ( n )+1;
int cnt = ( n+blo-1 )/blo;
for ( int i = 1; i <= blo; i ++ )
{
for ( int j = 0; j < n; j ++ )
sum[i][j] = sum[i-1][j];
for ( int j = ( i-1 )*cnt; j < n && j < i*cnt; j ++ )
sum[i][ a[j] ] ++;
}
for ( int i = 1; i <= blo; i ++ )
{
for ( int j = i; j <= blo; j ++ )
{
cc[i][j] = cc[i][j-1]; //注意初始化
mul[i][j] = mul[i][j-1];
for ( int k = ( j-1 )*cnt; k < n && k < j*cnt; k ++ )
{
int x = sum[j][ a[k] ]-sum[i-1][ a[k] ];
if ( x > cc[i][j] || x == cc[i][j] && mul[i][j] > a[k] )
{
cc[i][j] = x;
mul[i][j] = a[k];
}
}
}
}
int L, R, ans, anspos;
//ans次数最多,anspos次数最多且数字最小
while ( Q -- )
{
scanf ( "%d%d", &L, &R );
int x = L/cnt+1, y = R/cnt+1;
ans = cc[x+1][y-1], anspos = mul[x+1][y-1];
for ( int i = L; i <= R && i < x*cnt; i ++ )
tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
for ( int i = max ( L, ( y-1 )*cnt ); i <= R; i ++ )
tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
for ( int i = L; i < x*cnt && i <= R; i ++ )
{
tmp[ a[i] ] ++;
if ( tmp[ a[i] ] > ans || tmp[ a[i] ] == ans && a[i] < anspos )
{
ans = tmp[ a[i] ];
anspos = a[i];
}
}
if ( x != y ) //同块处理
{
for ( int i = ( y-1 )*cnt; i <= R; i ++ )
{
tmp[ a[i] ] ++;
if ( tmp[ a[i] ] > ans || tmp[ a[i] ] == ans && a[i] < anspos )
{
ans = tmp[ a[i] ];
anspos = a[i];
}
}
}
printf ( "%d %d\n", anspos, ans );
}
}
int main ( )
{
solve ( );
return 0;
}
当x值不在区间[0,n)时,我们可以通过离散将其映射到[0,n)中。
离散化代码:
memcpy ( b, a, sizeof ( a ) );
sort ( a, a+n );
for ( int i = 0; i < n; i ++ )
b[i] = lower_bound ( a, a+n, b[i] )-a;
由于我们很好的解决了不带修改的区间众数,那么那些预处理都是一样的,只不过我们需要做的是在修改时将sum和cc的值更新就行了,修改一个数时会影响blo^2个区间(blo为块数),如果blo=sqrt(n),那么复杂度为n,这样是不可接受的,我们将blo=n^(1/3),那么复杂度就为n^(2/3),如果我们同时预处理第i~j块数字x出现的次数(记bb[i][j][x]),第i~j块出现次数为x的个数(记tim[i][j][x]),当修改a[L]=R时(L属于第x块),将影响所有包含x块的区间块,由于bb[i][j][x]和tim[i][j][x]的修改很简单,那么cc[i][j]的值是怎样呢?由于最大次数的变化范围在[-1,1]之间(因为以前的数的次数减1,现在的数加1,众数个数可能不止一个,所以众数变化[-1,1]之内),所以可以直接判断tim的个数就可以求出cc的值,sum的值可以用一个循环更新,总的复杂度为O((n+q)*n^(2/3))。
在这里bb[i][j]和tim[i][j]的处理和前面cc[i][j]的处理是一样的,已经求出了i~j-1的值,只需要将bb[i][j-1],tim[i][j-1],cc[i][j-1]的值复制(注意开始写的时候很容易出现初始值初始错误的问题)。
#include
using namespace std;
const int maxn = 20005, N = 28;
int sum[N][maxn], a[maxn], cc[N][N];
int tmp[maxn], tim[N][N][maxn], bb[N][N][maxn];
void solve ( )
{
int n, Q;
//freopen ( "in0.in", "r", stdin );
//freopen ( "in1.out", "w", stdout );
scanf ( "%d%d", &n, &Q );
for ( int i = 0; i < n; i ++ )
scanf ( "%d", &a[i] );
int blo = ( int )pow ( n, 1.0/3 )+1;
int cnt = ( n+blo-1 )/blo;
for ( int i = 1; i <= blo; i ++ )
{
for ( int j = 0; j < n; j ++ )
sum[i][j] = sum[i-1][j];
for ( int j = ( i-1 )*cnt; j < n && j < i*cnt; j ++ )
sum[i][ a[j] ] ++;
}
for ( int i = 1; i <= blo; i ++ )
{
for ( int j = i; j <= blo; j ++ )
{
cc[i][j] = cc[i][j-1];
for ( int k = 0; k < n; k ++ ) //初始化
{
bb[i][j][k] = bb[i][j-1][k];
tim[i][j][k] = tim[i][j-1][k];
}
for ( int k = ( j-1 )*cnt; k < n && k < j*cnt; k ++ )
{
int p = bb[i][j][ a[k] ] ++;
tim[i][j][p] --;
tim[i][j][p+1] ++;
int x = sum[j][ a[k] ]-sum[i-1][ a[k] ];
if ( x > cc[i][j] )
cc[i][j] = x;
}
}
}
int op, L, R, ans;
while ( Q -- )
{
scanf ( "%d%d%d", &op, &L, &R );
if ( op )
{
int x = L/cnt+1, y = R/cnt+1;
ans = cc[x+1][y-1];
for ( int i = L; i <= R && i < x*cnt; i ++ )
tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
for ( int i = ( y-1 )*cnt; i <= R; i ++ )
tmp[ a[i] ] = max ( 0, sum[y-1][ a[i] ]-sum[x][ a[i] ] );
for ( int i = L; i < x*cnt && i <= R; i ++ )
{
tmp[ a[i] ] ++;
if ( tmp[ a[i] ] > ans )
ans = tmp[ a[i] ];
}
if ( x != y )
{
for ( int i = ( y-1 )*cnt; i <= R; i ++ )
{
tmp[ a[i] ] ++;
if ( tmp[ a[i] ] > ans )
ans = tmp[ a[i] ];
}
}
printf ( "%d\n", ans );
}
else
{
int p = a[L];
a[L] = R;
int x = L/cnt+1;
for ( int i = x; i <= blo; i ++ )
{
sum[i][p] --;
sum[i][R] ++;
}
for ( int i = 1; i <= blo; i ++ )
{
for ( int j = i; j <= blo; j ++ )
{
if ( i <= x && x <= j ) //包含x块
{
int t = bb[i][j][p] --;
tim[i][j][t] --; //次数和个数变化
tim[i][j][t-1] ++;
t = bb[i][j][R] ++;
tim[i][j][t] --;
tim[i][j][t+1] ++;
t = cc[i][j];
if ( tim[i][j][t-1] )//判断是否存在个数
cc[i][j] = t-1;
if ( tim[i][j][t] )
cc[i][j] = t;
if ( tim[i][j][t+1] )
cc[i][j] = t+1;
}
}
}
}
}
}
int main ( )
{
solve ( );
return 0;
}
2.如果还是需要得到次数最多且数字最小的数字时,可以将tim定义为set。