区间求和
该莫队以洛谷P1972 [SDOI2009]HH的项链为例。直接求和的莫队不需要cnt数组。
设置两个移动指针left和right,进行移动。若区间发生扩充,则加上移动位置的数组值,若区间发生缩减,则减去移动位置上数组的值。代码如下:
#define MAX 1e6
int init[MAX], cnt[MAX], now = 0, left = 1, right = 0; //init为原始数据,cnt为当前数值出现次数,now记录和
void add(int position){ //扩充时
if(!cnt[init[position]]) ++now;
++cnt[init[position]];
}
void del(int position){ //缩减时
--cnt[init[position]];
if(!cnt[init[position]]) --now;
}
void work() {//优化2主过程
for(int i = 1; i <= q; ++i) {//对于每次询问
int ql, qr;
scanf("%d%d", &ql, &qr);//输入询问的区间
while(left < ql) del(left++);//如左指针在查询区间左方,左指针向右移直到与查询区间左端点重合
while(left > ql) add(--left);//如左指针在查询区间左端点右方,左指针左移
while(right < qr) add(++right);//右指针在查询区间右端点左方,右指针右移
while(right > qr) del(right--);//否则左移
printf("%d\n", now);//输出统计结果
}
}
优化
NO.1
由于使用莫队时,通常数据量会比较大,因此需要使用快读,如下:
int read(){
char ch = getchar();
int res = 0;
bool flag = false;
while((ch < '0' || ch > '9') && ch != '-') ch = getchar();
if(ch == '-') flag = true;
while(ch >= '0' && ch <= '9'){
res += res * 10 + ch - '0';
ch = getchar();
}
if(flag) res *= -1;
return res;
}
NO.2
对原始数组按照端点顺序进行排序:左端点不同时,按照左端点从小到大排序;左端点相同时,若左端点为奇数,则按右端点升序排序,若左端点为偶数,则按右端点降序排序。sort()函数的cmp()部分如下:
struct visit{
int left, right; //记录访问区间的左右端点值
}visits[MAX];
bool cmp(struct visit a, struct visit b){
if(a.left != b.left) return a.left < b.left; //左端点不同,按左端点升序排
else if(a.left & 1) return a.right < b.right; //左端点相同且为奇数,按右端点升序排
else return a.right > b.right; //左端点相同且为偶数,按右端点降序排
}
NO.3
开启O2优化#pragma GCC optimize(2)
NO.4
由于指针移动过程中,需要不断调用add()
和del()
函数,所以比较耗时。因此利用符号优先级去掉两个函数的调用过程,直接在while
中实现加减过程,如下:
while(left < ql) now -= !--cnt[init[left++]];
while(left > ql) now += !cnt[init[--left]]++;
while(right < qr) now += !cnt[init[++right]]++;
while(right > qr) now -= !--cnt[init[right--]];