题面
分析
莫队,往死里卡常,开O2加奇偶性优化可卡过我才不会告诉你我这道题提交了37次呢
code
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;i++)
#define anti_loop(i,start,end) for(register int i=start;i>=end;i--)
#define clean(arry,num); memset(arry,num,sizeof(arry));
const int maxn=500000+10,maxm=500000+10;
int n,m,kinds=0;
int vis[10000001+100];
int a[maxn];
int ans[maxn];
int block;
struct node{int l;int r;int q;int bl;}e[maxm];
inline int read()
{
int ans=0;
char r=getchar();bool neg=false;
while(r<'0'||r>'9'){if(r=='-')neg=true;r=getchar();}
while(r>='0'&&r<='9'){ans=ans*10+r-'0';r=getchar();}
return (neg)?-ans:ans;
}
void write(int x){
if(x<0){putchar('-');x=-x;}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline bool cmp(node a,node b)
{
return (a.bl)^(b.bl)?a.l<b.l:(((a.bl)&1)?a.r<b.r:a.r>b.r);
}
inline void add(int pos)
{
if((++vis[a[pos]])==1)++kinds;
}
inline void del(int pos)
{
if((--vis[a[pos]])==0)--kinds;
}
int main()
{
//freopen("data.txt","r",stdin);
n=read();clean(vis,false);
//block=sqrt(n);
//block=n/sqrt(n*2.0/3);
loop(i,1,n)a[i]=read();
m=read();
loop(i,1,m)
{
e[i].q=i;
e[i].l=read();
e[i].r=read();
e[i].bl=((e[i].l-1)/sqrt(n)/2)+1;
}
sort(e+1,e+1+m,cmp);
int nl=0;int nr=0;
loop(i,1,m)
{
int l=e[i].l;int r=e[i].r;
while(nl<l)del(nl++);
while(nl>l)add(--nl);
while(nr>r)del(nr--);
while(nr<r)add(++nr);
ans[e[i].q]=kinds;
}
loop(i,1,m){write(ans[i]);printf("\n");}
return 0;
}
学到的东西
- 各种卡常技巧
1.inline和register
2.自增减运算符前置(a++不如写++a)
3.位运算加速
4.快读快输加速(快输用递归居然比数组快!)
5.ios语句对cin,cout的优化(用快读的绕过此处~~)
- 莫队的各种优化
1.奇偶性优化
莫队玄学奇偶性排序
这个可以帮你每个点平均优化200ms(可怕)
主要操作:把查询区间的排序函数
int cmp(query a, query b) {
return belong[a.l] == belong[b.l] ? a.r < b.r : belong[a.l] < belong[b.l];
}
二话不说,直接删掉,换成
int cmp(query a, query b) {
return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r);
}
也就是说,对于左端点在同一奇数块的区间,右端点按升序排列,反之降序。蒟蒻以前一直不懂这个优化是什么原理,所以采用了最笨的方法,画图模拟。。
为什么它的效果这么好?请看下图
最上左边是一组询问,中间是莫队的普通排序,右边是莫队的奇偶性排序
下面两个图分别是对同一组数据莫队的普通排序和奇偶性排序中右指针R随分块序号B的变化规律
从中不难看出,相比起普通排序,奇偶排序的波峰波谷少了将近50%,R指针的位移也少了将近一半
它的主要原理便是右指针跳完奇数块往回跳时在同一个方向能顺路把偶数块跳完,然后跳完这个偶数块又能顺带把下一个奇数块跳完。
由于一般排列中的R在同一block里单调递增,我们可以将R在一个block里的变化看做一座陡峭的山峰,而实际情况下常常有许多山峰,我们将其编号1到n,现在把相邻两座i,i+1(i奇数,i+1偶数)山峰中的i+1座山峰转体180度,和i合在一起(就像上图左下角一样),这样就减少了我们爬过这所有山的难度(实在不行,手画一下图秒懂)
这里有个洛谷某大佬写的较为清晰的cmp函数,可以借鉴,这样子比较不容易出错
inline bool cmp(node a,node b)
{
if(a.block==b.block)
{
if(a.block%2==1)
{
return a.r<b.r;
}
else
{
return a.r>b.r;
}
}
else
{
return a.l<b.l;
}
}
2.块优化(原创)
一般我们分块时用一个block计算一下块的大小,然后cmp里面就要不断用l来除以block,这样就加大了计算量,不如直接在预处理的时候就把每个节点的block放在结构体里面,这样可以少算一些
//原版
inline bool cmp(node a,node b)
{
return (a.l/block)^(b.l/block)?a.l<b.l:(((a.l/block)&1)?a.r<b.r:a.r>b.r);
}
....
int main()
{
....
block=n/sqrt(n*2.0/3);
....
loop(i,1,m)
{
e[i].q=i;
e[i].l=read();
e[i].r=read();
}
sort(e+1,e+1+m,cmp);
}
//新版
inline bool cmp(node a,node b)
{
return (a.bl)^(b.bl)?a.l<b.l:(((a.bl)&1)?a.r<b.r:a.r>b.r);
}
int main()
{
....
loop(i,1,m)
{
e[i].q=i;
e[i].l=read();
e[i].r=read();
e[i].bl=((e[i].l-1)/sqrt(n)/2)+1;
}
....
sort(e+1,e+1+m,cmp);
return 0;
}
(若有谬误,请及时于下方评论,博主好及时修改)