您的位置:首页 > 其它

bzoj 2038 小z的袜子 莫队例题

2015-02-22 14:30 197 查看
莫队,利用可以快速地通过一个问题的答案得到另一问题的答案这一特性,合理地组织问题的求解顺序,将已解决的问题帮助解决当前问题,来优化时间复杂度。

典型用法:处理静态(无修改)离线区间查询问题。

线段树也是处理区间问题的一个有力工具,它和莫队算法各有特点:

线段树可以支持修改,并且单次操作时间复杂度一般为O(log),支持在线,但是要求可以进行快速的区间合并操作,两个区间如不能快速合并(f(n)*O(log)>O(n)),则用线段树就没有什么实际价值了(暴力都比它块)

莫队算法可以解决某些线段树不能解决的静态离线问题,但它要求可以快速地从一个答案得到另一个答案。

对于区间问题,假如我们得到了区间[l,r]的答案,能通过它用O(1)的时间得到[l-1,r],[l+1,r],[l,r-1],[l,r+1]的答案,那么我们将[l,r]看成二维平面上的点,两个点的距离用哈密顿距离表示,一个不错的想法是找到图的最小生成树,然后暴力推出一个点,其它点从它延伸过去就行了,时间复杂度是距离和加上暴力的那个点花的时间。

这道题除了上面的做法,还可以分块,如果分成n^0.5块,可以做到O(n^1.5)的复杂度。

做法是先将原颜色序列分成根号n块,然后将询问先按左端点排序,对于每一块的询问再按右端点排序(都是升序)。

每次计算一个左端点在一个块中的询问,先暴力这个区间的第一个询问,然后后面的每个询问从它前一个询问推(具体看代码)

时间复杂度可以这么看:

排序O(nlogn)

对于每个询问,它从前一个推过来,因为它们在同一块中,前端点改变最多O(n^0.5)次,有O(n)个询问,所以前端点变化O(n*n^0.5)次

对于每一段,后端点变化是O(n)的,而最多有O(n^0.5)段,所以后端点变化O(n^0.5*n)次

所以从一个[l,r]推到它四个相邻的点的次数是O(n^1.5),而转移是O(1)的,所以总的复杂度是O(n^1.5)。

不论是分块还是最小生成树,都是想用最少的转移和最少的暴力将所有询问解决。

不会写最小生成树的做法。这个是分块,感谢proverbs

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define maxn 100010
using namespace std;

typedef long long lng;

struct Qu {
int l, r, id;
};
bool cmpl( const Qu & a, const Qu & b ) {
return a.l < b.l;
}
bool cmpr( const Qu & a, const Qu & b ) {
return a.r < b.r;
}

int n, m;
int idx[maxn], itot;
int clr[maxn];
lng cnt[maxn];
int lx[maxn], rx[maxn], stot;
Qu qu[maxn];
lng ans[maxn];

void partition() {
int len = (int)ceil(sqrt(n));
stot = n/len;
rx[0] = 0;
for( int i=1; i<=stot; i++ ) {
lx[i] = rx[i-1]+1;
rx[i] = rx[i-1]+len;
}
if( rx[stot]!=n ) {
stot++;
lx[stot] = rx[stot-1]+1;
rx[stot] = n;
}
}
void makeid() {
sort( idx+1, idx+1+n );
int tot = unique( idx+1, idx+1+n ) - idx;
for( int i=1; i<=n; i++ )
clr[i] = lower_bound( idx+1, idx+tot, clr[i] ) - idx;
}
lng cube( lng a ) {
return a*a*a;
}
void update( lng &sum, int c, int delta ) {
sum -= cube(cnt[c]);
cnt[c] += delta;
sum += cube(cnt[c]);
}
void work() {
sort( qu+1, qu+1+m, cmpl );
for( int i=1,s=1,t=1; i<=stot; i++ ) {
memset( cnt, 0, sizeof(cnt) );
while( s<=m && qu[s].l<lx[i] ) s++;
while( t<=m && qu[t].l<=rx[i] ) t++;
sort( qu+s, qu+t, cmpr );

lng sum = 0;
for( int j=qu[s].l; j<=qu[s].r; j++ )
update( sum, clr[j], +1 );
ans[qu[s].id] = sum;
for( int q=s+1; q<t; q++ ) {
if( qu[q].l<=qu[q-1].l ) {
//    [ ( ) ]
for( int j=qu[q].l; j<qu[q-1].l; j++ )
update( sum, clr[j], +1 );
for( int j=qu[q-1].r+1; j<=qu[q].r; j++ )
update( sum, clr[j], +1 );
} else if( qu[q].l>qu[q-1].r ) {
//    ( ) [ ]
for( int j=qu[q-1].l; j<=qu[q-1].r; j++ )
cnt[clr[j]]--;
sum = 0;
for( int j=qu[q].l; j<=qu[q].r; j++ )
update( sum, clr[j], +1 );
} else {
//    ( [ ) ]
for( int j=qu[q-1].l; j<qu[q].l; j++ )
update( sum, clr[j], -1 );
for( int j=qu[q-1].r+1; j<=qu[q].r; j++ )
update( sum, clr[j], +1 );
}
ans[qu[q].id] = sum;
}
}
}
int main() {
scanf( "%d", &n );
for( int i=1; i<=n; i++ ) {
scanf( "%d", idx+i );
clr[i] = idx[i];
}
scanf( "%d", &m );
for( int i=1; i<=m; i++ ) {
scanf( "%d%d", &qu[i].l, &qu[i].r );
qu[i].id = i;
}
makeid();
partition();
work();
for( int i=1; i<=m; i++ )
printf( "%lld\n", ans[i] );
}


View Code
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: