您的位置:首页 > 编程语言 > Go语言

Codeforces 86D. Powerful array (MO's Algorithm(区间查询的离线优化))

2016-03-09 15:04 531 查看
题目链接: http://codeforces.com/problemset/problem/86/D

题意:给一个长度为n的数组(1 <= n <= 200000),有 t 个区间询问(1 <= t <= 200000),

每个询问要求指定区间的数对应的一个值,这个值 = 对区间内出现的每个值v,累加 V 乘上(V的出现次数的平方)。

刚刚学了MO's Algorithm,看文章说这题比较经典,就来做做看了。

MO's Algorithm:就是对于一个区间的询问来说,如果该询问支持Add和Remove(即,区间信息的维护支持加和减),就可以考虑使用这个算法。

假设有N个数,M个区间询问,该算法的时间复杂度是O((M+N)*sqrt(N))。

首先来说条件,这个询问必须支持Add和Remove,比如本题就支持。

举个不支持的例子,假设知道[2..5]这个区间的最大值和下标为5的数的数值,是没办法知道[2..4]这个区间的最大值的。

也就是说,最大值只能Add,不能Remove,于是求区间最大值不能用MO's Algorithm。

MO's Algorithm将所有的区间按照L值分成sqrt(n)组,同一组内按照R值来排序。

这样,维护全局变量L,R,以及[L,R]对应的答案RANS (组间要清空维护的这个答案,Clear()函数清空)。

依次改变L和R使之等于每个询问,然后记录下这个询问的答案即可。

这样做了之后,考虑按照L分成的第一组,第一组内的R值已经排了序,所以R是一直向右移动的,于是R最多移动次数为n。

一共sqrt(n)组,所以只考虑R的移动的话,时间复杂度O(n*sqrt(n))。

再来考虑L,每组内的L都在同一个sqrt(n)的范围内,所以每次询问,L最多移动sqrt(n)次(跨组的时候可能会达到2sqrt(n))。

有 t 个询问所以一共大概是 t * sqrt(n)+sqrt(n)次,时间复杂度O( t *sqrt(n))。

所以总复杂度O(( t +n)*sqrt(n))。

总的来说,MO's Algorithm只需3步:

1.区间排序

2.写好Add和Remove和Clear。

3.依次处理各个区间得到答案

处理区间的时候,我以为我是在优化,其实反而降低了速度。以下两份代码只有上面第三步不一样。

低速代码如下:

/*
2400 ms	8700 KB
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define maxn 200001
#define LL long long
using namespace std;
//题目数据
int n,t,p;
int a[maxn];
//Mo's Algotithm
LL ans[maxn];
struct Interval{
int L,R,id;
bool operator <(const Interval &B)const{
return L/p < B.L/p || L/p == B.L/p && R < B.R;
}
}I[maxn];
LL RANS;
int Num[1000001];
void Clear(){memset(Num,0,sizeof(Num));RANS=0;}
void Add(LL v){RANS+=((++Num[v]<<1)-1)*v;}
void Remove(LL v){RANS-=((--Num[v]<<1)+1)*v;}
int main(void)
{
while(~scanf("%d%d",&n,&t)){
p = sqrt(n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=t;++i)
scanf("%d%d",&I[I[i].id=i].L,&I[i].R);
//区间排序
sort(I+1,I+t+1);
//计算
int L,R=n+1;//[L,R]
for(int i=1;i<=t;++i){
if(R > I[i].R){//换区清盘
Clear();
L=1;R=0;
}
//修改边界
while(R < I[i].R) Add(a[++R]);
while(L < I[i].L) Remove(a[L++]);
while(L > I[i].L) Add(a[--L]);
//记录答案
ans[I[i].id]=RANS;
}
for(int i=1;i<=t;++i) printf("%I64d\n",ans[i]);
}
return 0;
}

快速:

/*
1996 ms	8600 KB
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define maxn 200001
#define LL long long
using namespace std;
//题目数据
int n,t,p;
int a[maxn];
//Mo's Algotithm
LL ans[maxn];
struct Interval{
int L,R,id;
bool operator <(const Interval &B)const{
return L/p < B.L/p || L/p == B.L/p && R < B.R;
}
}I[maxn];
LL RANS;
int Num[1000001];
void Clear(){memset(Num,0,sizeof(Num));RANS=0;}
void Add(LL v){RANS+=((++Num[v]<<1)-1)*v;}
void Remove(LL v){RANS-=((--Num[v]<<1)+1)*v;}
int main(void)
{
while(~scanf("%d%d",&n,&t)){
p = sqrt(n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=t;++i)
scanf("%d%d",&I[I[i].id=i].L,&I[i].R);
//区间排序
sort(I+1,I+t+1);

Clear();
//计算
int L=1,R=0;//[L,R]
for(int i=1;i<=t;++i){
//修改边界
while(R > I[i].R) Remove(a[R--]);
while(R < I[i].R) Add(a[++R]);
while(L < I[i].L) Remove(a[L++]);
while(L > I[i].L) Add(a[--L]);
//记录答案
ans[I[i].id]=RANS;
}
for(int i=1;i<=t;++i) printf("%I64d\n",ans[i]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息