扫描线再学习
前言
- 东西有点多渲染有点慢
- msjing好像在寒假听过 \(5\)_\(Lei\) 和 \(2K22\) 讲的扫描线,但是显然msjing忘完了
msjing:aaaaa集训要学好多啊好想打酸角洲
扫描线简介
- 扫描线具体是为了解决矩形面积并/周长,二维数点,\(B\) 维正交等问题,其维护多依据线段树,而其之所以叫扫描线,是因为其维护过程很像一根线在扫描
维护及基础应用
- 经典问题:给你 \(n\) 个矩形,求这 \(n\) 个矩形的并集覆盖总面积
- 扫描线的具体维护依据线段树,线段树要维护当前覆盖区间,就像线扫到哪了,贴一张广为流传的图
- 我们看这张图,线从上到下扫描,碰到哪根线了就更新当前覆盖区间,出去后就踢出,并记录在这个区间内覆盖了多少
- e,其他就没啥了,基础就是这些,贴代码吧
qwq
#include<bits/stdc++.h>
#define int long long
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
constexpr int maxn=2e6+10,p=1e9+7;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
struct _
{
int xa,xb,y,b;
bool operator < (_ a) const {return y<a.y;}
}lin[maxn];
struct __ {int l,r,sum,cnt;}tr[maxn];
int lc,cnt,n,ans,a[maxn],xa,xb,ya,yb;
void Pushup(int rt)
{
if (tr[rt].cnt) tr[rt].sum=a[tr[rt].r+1]-a[tr[rt].l];
else tr[rt].sum=tr[lson].sum+tr[rson].sum;
}
void Build(int rt,int l,int r)
{
tr[rt].l=l,tr[rt].r=r;
if (l == r) return;
int mid=(l+r) >> 1;
Build(lson,l,mid),Build(rson,mid+1,r);
}
void Upd(int rt,int l,int r,int val)
{
if (l<=tr[rt].l && tr[rt].r<=r)
{
tr[rt].cnt+=val;
Pushup(rt);
return;
}
int mid=(tr[rt].l+tr[rt].r) >> 1;
if (l<=mid) Upd(lson,l,r,val);
if (r>mid) Upd(rson,l,r,val);
Pushup(rt);
}
signed main()
{
n=read();
for (int i=1;i<=n;i++)
{
xa=read(),ya=read(),xb=read(),yb=read();
lc++;lin[lc]={xa,xb,ya,0};
lc++;lin[lc]={xa,xb,yb,1};
a[i]=xa,a[i+n]=xb;
}
sort(a+1,a+1+n*2);
cnt=unique(a+1,a+1+n*2)-a-1;
sort(lin+1,lin+1+n*2);
Build(1,1,cnt-1);
for (int i=1;i<=n*2;i++)
{
_ l=lin[i];
if (i>1) ans+=tr[1].sum*(lin[i].y-lin[i-1].y);
if (l.b)
{
int u,v;
u=lower_bound(a+1,a+1+cnt,l.xa)-a;
v=lower_bound(a+1,a+1+cnt,l.xb)-a-1;
Upd(1,u,v,-1);
}
else
{
int u,v;
u=lower_bound(a+1,a+1+cnt,l.xa)-a;
v=lower_bound(a+1,a+1+cnt,l.xb)-a-1;
Upd(1,u,v,1);
}
}
printf("%lld\n",ans);
return 0;
}
2K22的直接考古掏过来
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int o=2222222;
//对于y从小到大递增的过程,每一次y的增大都要重新判断x轴被覆盖的大小
//将一个矩形拆成两条线段,在y=ya的时候插入一条覆盖[xl,xr]的线段
//然后在y=yb的时候把插入的线段撤销,这个撤销的线段打个标记
struct myline{
ll xa;
ll xb;
ll y;
bool b;
bool operator<(myline a)const{
return y<a.y;
}
}l[o];//对于线段先排个序
struct node{
int l;
int r;
ll len;
ll cnt;
//这个cnt其实只是管区间是否完全覆盖的 方便对于整段线段的修改
}t[o];
int lc,n,cnt;
ll a[o],xa,ya,xb,yb,ans;
void build(int p,int l,int r){//建树
t[p].l=l;t[p].r=r;
if(l==r)return ;
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void pushup(int p){
if(t[p].cnt) t[p].len=a[t[p].r+1]-a[t[p].l];//完全覆盖
else t[p].len=t[p*2].len+t[p*2+1].len;//不一定完全覆盖
}
void change(int p,int l,int r,int v){
if(t[p].l>=l&&t[p].r<=r){
t[p].cnt+=v;//这个是更新被覆盖的次数的
pushup(p);//一定要更新一下当前节点区间被覆盖的次数和长度
return ;
}
int mid=(t[p].l+t[p].r)/2;
if(l<=mid)change(p*2,l,r,v);
if(r>mid)change(p*2+1,l,r,v);
pushup(p);
}
void in(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld%lld%lld",&xa,&ya,&xb,&yb);
//先插入一条线段
l[++lc].xa=xa;
l[lc].xb=xb;
l[lc].y=ya;
l[lc].b=0;
//再撤销一条线段
l[++lc].xa=xa;
l[lc].xb=xb;
l[lc].y=yb;
l[lc].b=1;
//离散化
a[i]=xa;
a[i+n]=xb;
}
}
void pre(){
sort(a+1,a+2*n+1);
cnt=unique(a+1,a+2*n+1)-a-1;
//离散化就不多讲了
sort(l+1,l+2*n+1);
//扫描线一定是一个有序的处理过程,在某个点把与该点有关的全部改好
//所以记得先排个序
build(1,1,cnt-1);
}
void work(){
for(int i=1;i<=2*n;i++){
myline nowline=l[i];//取出一条线段
if(i>1)ans+=t[1].len*(l[i].y-l[i-1].y);//累计答案,注意离散化
//求y=yi时的情况一定要找当前被覆盖的总长度
//和普通的线段树不太一样,这个线段树看起来有点神奇,是因为查询只用查根节点,所以直接取了这个
//正常的线段树是需要打lazy的,这个不一样是因为这个update函数特殊,所以没有必要下传lazy标记
if(nowline.b)change(1,lower_bound(a+1,a+cnt+1,nowline.xa)-a,lower_bound(a+1,a+cnt+1,nowline.xb)-a-1,-1);
//上面这个是撤销一条线段,显然就是把线段覆盖的部分去掉,加个-1就是撤销
else change(1,lower_bound(a+1,a+cnt+1,nowline.xa)-a,lower_bound(a+1,a+cnt+1,nowline.xb)-a-1,1);
//这个就是正经的插入一条线段
}
}
void out(){
cout<<ans;
}
int main(){
in();
pre();
work();
out();
return 0;
}
- 非常有辨识度的 \(2222222\)
- 这里还有别的代码和有注释版的,可以去看一看
- 解释一下 \(pushup\) 中的 \(r+1\),e,这是因为线段树的区间维护的并不是单个点(?)
小剧场
-
sbmqwm:msjing你讲明白了吗,别跑
-
msjing:aiaien~
-
\(from\) 捏造:老传统了不用管
周长
- e,就是上下扫完左右扫,扫扫扫
- 给代码吧,e
之前写的好丑
#include<bits/stdc++.h>
using namespace std;
#define lson (rt << 1)
#define rson (rt << 1 | 1)
int n;
int a1[2000010];
int a2[2000010];
int cnt1,cnt2;
long long xa,xb,ya,yb;
long long ans;
struct node
{
int l,r,sum,cnt;
}tree[2000010];
struct node1
{
int l,r,sum,cnt;
}tree1[2000010];
struct lline1
{
int xa,xb,y;
bool b;
bool operator<(lline1 a)const
{
return y<a.y;
}
}line1[2000010];
struct lline2
{
int ya,yb,x;
bool b;
bool operator<(lline2 a)const
{
return x<a.x;
}
}line2[2000010];
void pushup1(int rt)
{
if (tree[rt].cnt) tree[rt].sum = a1[tree[rt].r+1]-a1[tree[rt].l];
else tree[rt].sum = tree[lson].sum+tree[rson].sum;
}
void pushup2(int rt)
{
if (tree1[rt].cnt) tree1[rt].sum = a2[tree1[rt].r+1]-a2[tree1[rt].l];
else tree1[rt].sum = tree1[lson].sum+tree1[rson].sum;
}
void build1(int rt,int l,int r)
{
tree[rt].l=l;
tree[rt].r=r;
if (l==r) return;
int mid=(l+r) >> 1;
build1(lson,l,mid);
build1(rson,mid+1,r);
}
void build2(int rt,int l,int r)
{
tree1[rt].l=l;
tree1[rt].r=r;
if (l==r) return;
int mid=(l+r) >> 1;
build2(lson,l,mid);
build2(rson,mid+1,r);
}
void update1(int rt,int l,int r,int val)
{
if (l<=tree[rt].l && tree[rt].r<=r)
{
tree[rt].cnt += val;
pushup1(rt);
return;
}
int mid=(tree[rt].l+tree[rt].r) >> 1;
if (l<=mid) update1(lson,l,r,val);
if (r>mid) update1(rson,l,r,val);
pushup1(rt);
}
void update2(int rt,int l,int r,int val)
{
if (l<=tree1[rt].l && tree1[rt].r<=r)
{
tree1[rt].cnt += val;
pushup2(rt);
return;
}
int mid=(tree1[rt].l+tree1[rt].r) >> 1;
if (l<=mid) update2(lson,l,r,val);
if (r>mid) update2(rson,l,r,val);
pushup2(rt);
}
int main()
{
cin >> n;
for (int i=1;i<=n;i++)
{
cin >> xa >> ya >> xb >> yb;
line1[i].xa=xa;
line1[i].xb=xb;
line1[i].y = ya;
line1[i].b=false;
line1[i+n].xa=xa;
line1[i+n].xb=xb;
line1[i+n].y = yb;
line1[i+n].b=true;
a1[i]=xa;
a1[i+n]=xb;
line2[i].ya=ya;
line2[i].yb=yb;
line2[i].x = xa;
line2[i].b=false;
line2[i+n].ya=ya;
line2[i+n].yb=yb;
line2[i+n].x = xb;
line2[i+n].b=true;
a2[i]=ya;
a2[i+n]=yb;
}
stable_sort(a1+1,a1+1+n*2);
cnt1 = unique(a1+1,a1+1+n*2)-a1-1;
stable_sort(line1+1,line1+1+n*2);
build1(1,1,cnt1-1);
stable_sort(a2+1,a2+1+n*2);
cnt2 = unique(a2+1,a2+1+n*2)-a2-1;
stable_sort(line2+1,line2+1+n*2);
build2(1,1,cnt2-1);
for (int i=1;i<=n*2;i++)
{
int t=tree[1].sum;
lline1 l = line1[i];
if (l.b)
{
int u,v;
u = lower_bound(a1+1,a1+1+cnt1,l.xa)-a1;
v = lower_bound(a1+1,a1+1+cnt1,l.xb)-a1-1;
update1(1,u,v,-1);
}
else
{
int u,v;
u = lower_bound(a1+1,a1+1+cnt1,l.xa)-a1;
v = lower_bound(a1+1,a1+1+cnt1,l.xb)-a1-1;
update1(1,u,v,1);
}
ans += abs(tree[1].sum-t);
}
for (int i=1;i<=n*2;i++)
{
int t = tree1[1].sum;
lline2 l = line2[i];
if (l.b)
{
int u,v;
u = lower_bound(a2+1,a2+1+cnt2,l.ya)-a2;
v = lower_bound(a2+1,a2+1+cnt2,l.yb)-a2-1;
update2(1,u,v,-1);
}
else
{
int u,v;
u = lower_bound(a2+1,a2+1+cnt2,l.ya)-a2;
v = lower_bound(a2+1,a2+1+cnt2,l.yb)-a2-1;
update2(1,u,v,1);
}
ans += abs(tree1[1].sum-t);
}
cout << ans << endl;
return 0;
}
逆序对
- 其实可以理解为一维的扫描线,(但是这是一个二维数点题qwq),从上到下,e,相当于是什么有一个数出现就统计答案,如果不能理解请到正规题解查看
- 逆序对还能用归并做,但是由于写的时间太古早了,所以不给了
- e,给代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
constexpr int maxn=3e6+10,p=1e9+7;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,a[maxn],b[maxn],c[maxn];
int lowbit(int x) {return x&(-x);}
void add(int x,int v) {while (x<=n) c[x]+=v,x+=lowbit(x);}
int query(int x) {int res=0;while (x) res+=c[x],x-=lowbit(x);return res;}
signed main()
{
n=read();for (int i=1;i<=n;i++) a[i]=b[i]=read();
sort(a+1,a+1+n);
int cnt=unique(a+1,a+1+n)-a-1;
for (int i=1;i<=n;i++) b[i]=lower_bound(a+1,a+1+cnt,b[i])-a;
int ans=0;
for (int i=n;i>=1;i--) ans+=query(b[i]-1),add(b[i],1);
printf("%lld\n",ans);
return 0;
}
- 还有13倍经验
二维数点
给一个长为 \(n\) 的序列,有 \(m\) 次查询,每次查区间 \([l,r]\) 中值在 \([x,y]\) 内的元素个数
- 这样的问题称为二维数点
- 诶,并不是二维的a
- 其实我们可以利用扫描线改为二维,然后就可以搞了
- 怎么搞捏,欸,把二维变一维
- 好诡异,但是其实是扫一维,\(DS\) 维护一维,在处理上就酱味了
- sbmqwm:酱味大鸡!
- msjing:懂你意思
- e,没啥了,例题再补吧
例题
P1972 [SDOI2009] HH 的项链
- 史上超级 \(DS\) 神题,可以用包括但不限于树状数组,线段树,主席树,分块莫队,前缀做,我们说树状数组离线做法
- 为什么讲扫描线要说树状数组捏,因为其本质上可以理解为扫描线(其实是二维数点)
- 询问在线搞显然不现实,(貌似主席树可以在线?),所以离线搞,对询问的所有右端点排序,不断去扫原珍珠序列,如果扫到了一个珍珠之前出现过,那这个就没用了,更新一下位置就行,扫完一个询问后统计答案
- 用二维数点实质上理解一下,颜色和位置各一维,\(DS\) 维护一维,扫一维,算 \(r\) 的矩形减去 \(l\) 的矩形,贴张图

之前写的代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[1100010];
int pd[1100010];
int c[1100010];
int ans[1100010];
struct xxl
{
int r;
int l;
int num;
}xl[1100010];
bool cmp(xxl a,xxl b)
{
return a.r<b.r;
}
int lowbit(int x)
{
return (x&(-x));
}
void add(int w,int x)
{
while(w <= n)
{
c[w] += x;
w += lowbit(w);
}
}
int getsum(int w)
{
int res=0;
while(w)
{
res += c[w];
w -= lowbit(w);
}
return res;
}
int main()
{
cin >> n;
for (int i=1;i<=n;i++) cin >> a[i];
cin >> m;
for (int i=1;i<=m;i++)
{
cin >> xl[i].l >> xl[i].r;
xl[i].num = i;
}
sort(xl+1,xl+1+m,cmp);
int k=1;
for (int i=1;i<=m;i++)
{
for (int j=k;j<=xl[i].r;j++)
{
if (pd[a[j]]) add(pd[a[j]],-1);
add(j,1);
pd[a[j]]=j;
}
k = xl[i].r+1;
ans[xl[i].num] = getsum(xl[i].r) - getsum(xl[i].l-1);
}
for (int i=1;i<=m;i++) cout << ans[i] << endl;
return 0;
}
P4113 [HEOI2012] 采花
- 再来一道,继续扫描线暴力屮
- 读题,现在要两朵花,不是一个了,好搞,直接把上一题思路偷过来,然后把维护上一个珍珠的数组变成两个,一个维护上一个花出现的位置,一个维护上上次,当上上次的还没被更新时,仅更新数组,不管 \(DS\),因为这时对答案没影响
- 当上上次数组有东西但是上次没有时,这时会对答案有影响,\(DS\) 更新上上上次
- 当上次也要更新时,把上上次从 \(DS\) 踢出去,然后把上次加入,更新数组,就搞完了
sbmqwm:搞完!- msjing:。。。
- \(DS\) 随便选,线段树 \(BIT\) 啥的
点击查看代码
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,c,m,a[maxn];
struct BIT
{
int c[maxn];
int lowbit(int x) {return x&(-x);}
void add(int x,int v) {while (x<=n) c[x]+=v,x+=lowbit(x);}
int query(int x)
{
int res=0;
while (x)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
}B;
struct __ {int l,r,id;}qu[maxn];
bool cmp(__ a,__ b) {return a.r<b.r;}
int lt[maxn],nw[maxn],ans[maxn];
int main()
{
n=read(),c=read(),m=read();for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=m;i++)
{
int l=read(),r=read();
qu[i]={l,r,i};
}
sort(qu+1,qu+1+m,cmp);
int k=1;
for (int i=1;i<=m;i++)
{
for (int j=k;j<=qu[i].r;j++)
{
if (!lt[a[j]]) lt[a[j]]=j;
else
{
if (!nw[a[j]])
{
nw[a[j]]=j;
B.add(lt[a[j]],1);
}
else
{
B.add(lt[a[j]],-1);
B.add(nw[a[j]],1);
lt[a[j]]=nw[a[j]];
nw[a[j]]=j;
}
}
}
ans[qu[i].id]=B.query(qu[i].r)-B.query(qu[i].l-1);
k=qu[i].r+1;
}
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
P4137 Rmq Problem / mex
莫队二分暴力屮- 考虑高效维护区间,仍然扫描线思想,从左往右扫,但是需要记录左端点的改变情况并高效维护区间最值,选用权值线段树,记录左端点改变的最后位置,单点覆盖
- 查询答案时,查询询问的左端点,若一个点更新在其前,合法,线段树最小值求很快
- 具体实现时,若查询的左区间大于左子树最小值,左子树合法,向左子树递归,否则向右
点击查看代码
#include<bits/stdc++.h>
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
constexpr int maxn=2e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,a[maxn];
struct segtree
{
struct _ {int l,r,mn;}tr[maxn];
void pushup(int rt) {tr[rt].mn=min(tr[lson].mn,tr[rson].mn);}
void build(int rt,int l,int r)
{
tr[rt].l=l,tr[rt].r=r;
if (l == r) return;
int mid=(l+r) >> 1;
build(lson,l,mid),build(rson,mid+1,r);
pushup(rt);
}
void upd(int rt,int pos,int v)
{
if (tr[rt].l == tr[rt].r) return tr[rt].mn=v,void();
int mid=(tr[rt].l+tr[rt].r) >> 1;
if (pos<=mid) upd(lson,pos,v);
else upd(rson,pos,v);
pushup(rt);
}
int query(int rt,int v)
{
if (tr[rt].l == tr[rt].r) return tr[rt].l;
if (tr[lson].mn<v) return query(lson,v);
else return query(rson,v);
}
}T;
struct __ {int l,r,id;}qu[maxn];
bool cmp(__ a,__ b) {return a.r<b.r;}
int lt[maxn],nw[maxn],ans[maxn];
int main()
{
n=read(),m=read();for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=m;i++)
{
int l=read(),r=read();
qu[i]={l,r,i};
}
T.build(1,0,n+1);
sort(qu+1,qu+1+m,cmp);
int k=1;
for (int i=1;i<=m;i++)
{
for (int j=k;j<=qu[i].r;j++) T.upd(1,a[j],j);
ans[qu[i].id]=T.query(1,qu[i].l);
k=qu[i].r+1;
}
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
P2163 [SHOI2007] 园丁的烦恼
- 二维数点模板
- 我们扫描 \(X\),\(y\) 用 \(DS\) 维护一下,然后没了,最难的是离散化
- 解释一下为什么可以用扫描线
- 注意到查询是一个矩形,我们可以用前缀差分的思想快速求两个区间然后减
点击查看代码
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
constexpr int maxn=2e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int lim;
struct BIT
{
int c[maxn];
int lowbit(int x) {return x&(-x);}
void upd(int x,int v) {while (x<=lim) c[x]+=v,x+=lowbit(x);}
int que(int x)
{
int res=0;
while (x)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
}B;
int n,m,x[maxn],y[maxn],_x[maxn],_y[maxn],tt1,tt2;
struct _ {int a,b,c,d;}qu[maxn],qul[maxn];
struct __ {int l,r,lsum,rsum;}q[maxn];
vector<int> tr[maxn],l[maxn],r[maxn];
int main()
{
n=read(),m=read();for (int i=1;i<=n;i++) x[i]=_x[i]=read(),y[i]=_y[i]=read();
tt1=tt2=n;
for (int i=1;i<=m;i++)
{
int a=_x[++tt1]=read(),b=_y[++tt2]=read(),c=_x[++tt1]=read(),d=_y[++tt2]=read();
qu[i]={a,b,c,d};
}
sort(_x+1,_x+1+tt1),sort(_y+1,_y+1+tt2);
int k1=unique(_x+1,_x+1+tt1)-_x-1,k2=unique(_y+1,_y+1+tt2)-_y-1;
for (int i=1;i<=n;i++) x[i]=lower_bound(_x+1,_x+1+k1,x[i])-_x,y[i]=lower_bound(_y+1,_y+1+k2,y[i])-_y;
for (int i=1;i<=m;i++)
{
int a,b,c,d;
a=lower_bound(_x+1,_x+1+k1,qu[i].a)-_x;
b=lower_bound(_y+1,_y+1+k2,qu[i].b)-_y;
c=lower_bound(_x+1,_x+1+k1,qu[i].c)-_x;
d=lower_bound(_y+1,_y+1+k2,qu[i].d)-_y;
qu[i]={a,b,c,d};
}
lim=max(k1,k2);
for (int i=1;i<=n;i++) tr[x[i]].pb(y[i]);
for (int i=1;i<=m;i++) l[qu[i].a-1].pb(i),r[qu[i].c].pb(i);
for (int i=1;i<=lim;i++)
{
for (auto j:tr[i]) B.upd(j,1);
for (auto j:l[i]) q[j].lsum=B.que(qu[j].d)-B.que(qu[j].b-1);
for (auto j:r[i]) q[j].rsum=B.que(qu[j].d)-B.que(qu[j].b-1);
}
for (int i=1;i<=m;i++) printf("%d\n",q[i].rsum-q[i].lsum);
return 0;
}
P3722 [AHOI2017/HNOI2017] 影魔
- upd on 26.8.18 21:00 :我™拍了半个小时发现是密码的
printf没写%lld - 其实这题在主席树题单里,但是又没有要求强制在线,我直接离线
- 这个题意太抽象了,我翻译一下
Yn风格的翻译
- 题目背景
- T1.Gumayusi 获得2025英雄联盟全球总决赛 \(FMVP\)

- 为什么 \(Faker\) 的冠军加李奥 \(R\) 特效不是出现一个 \(LPL\) 标志然后落地击碎我不认可
- 但其实芸阿娜这皮还挺好用的hhh
- 题目描述
- 你在打 LOL,欣赏芸阿娜的绝美冠军皮,突然你家的哈吉米把你网线拽跑了,你找不到网线了,所以你决定先想一道数据结构题
- 给你一个长 \(n\) 的序列 \(k\),组成一个 \(1\) 到 \(n\) 的排列,对于一个二元组 \((i,j)\)(\(i < j\)),如果 \(k_i\) 和 \(k_j\) 为这个区间 \([i,j]\) 的最大值和次大值(不对应,即只要这俩包揽最大值和次大值就行),会产生 \(p_1\) 的贡献,如果 \(k_i\) 和 \(k_j\) 刚好有一个是这个区间 \([i,j]\) 的最大值,会产生 \(p_2\) 的贡献
- 现在给你 \(m\) 个询问,包含一个区间 \([a,b]\),求满足 \(a \le i < j < \le b\) 中所有贡献的和
- 输入格式
- 第一行四个整数 \(n,m,p_1,p_2\)
- 第二行 \(n\) 个整数 \(k_1, k_2,\cdots, k_n\)
- 接下来 \(m\) 行,每行两个数 \(a,b\),表示询问区间 \([a,b]\) 中贡献和
- \(O(n^3)\) 的暴力挺好想,直接枚举,\(O(n^3)\) 都会了,用线段树维护最大值可以降到 \(O(n^2 \log n)\)
- 然后就到瓶颈了,你发现没法优化了,所以我们转化一下思想
- 考虑一个答案的贡献
- 对于 \(p_1\) 贡献,单点显然成立,即 \((i,i+1)\),因为就俩点
- 那对与区间呢?由于贡献产生时必须使两个数一个最大一个次大,那我们预处理两个数组 \(L,R\),\(L_i,R_i\) 表示左右边第一个大于 \(k_i\) 的位置
- 为什么这么做?
- 对于 \(p_1\) 贡献,一种情况我们上面已经说了,由于要求,这样我们相当于使区间 \([L_i,R_i]\) 合法,因为区间内除了左右端点没有比 \(k_i\) 大的了
- 再说 \(p_2\),由于上面预处理的数组,我们可以知道 \(([L_i + 1,i-1],R_i\) 和 \((L_i,[i+1,R_i])\) 这两组二元组可以贡献 \(p_2\),就是固定一个最大端点动另一个
- 接下来就是维护了
- 单点直接算就行
- 我们将操作以及贡献区间离线,排序,做扫描线
- 对于 \(p_1\) 的第二种情况,直接扫到 \(R_i\) 后在 \(L_i\) 上加上贡献
- 对于两种 \(p_2\),在到 \(L_i\) 时候对区间 \([i+1,R_i-1]\) 直接加上贡献,另一个同理
- 对了,由于可能会算重,记得减去 \(l-1\) 的贡献
q[++tot]={r,l,r,1,i};
q[++tot]={l-1,l,r,-1,i};
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn=4e7+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,p1,p2;
int a[maxn];
int L[maxn],R[maxn];
int stk[maxn],top;
namespace sgt
{
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((l+r)>>1)
int s[maxn],tg[maxn];
void Pus(int rt) {s[rt]=s[ls]+s[rs];}
void Pud(int rt,int l,int r)
{
if (!tg[rt]) return;
int lz=tg[rt];
tg[rt]=0;
tg[ls]+=lz,tg[rs]+=lz;
s[ls]+=(mid-l+1)*lz;
s[rs]+=(r-mid)*lz;
}
void Upd(int rt,int l,int r,int L,int R,int v)
{
if (L<=l && r<=R)
{
tg[rt]+=v;
s[rt]+=(r-l+1)*v;
return;
}
Pud(rt,l,r);
if (L<=mid) Upd(ls,l,mid,L,R,v);
if (R>mid) Upd(rs,mid+1,r,L,R,v);
Pus(rt);
}
int que(int rt,int l,int r,int L,int R)
{
int res=0;
if (L<=l && r<=R) return s[rt];
Pud(rt,l,r);
if (L<=mid) res+=que(ls,l,mid,L,R);
if (R>mid) res+=que(rs,mid+1,r,L,R);
return res;
}
}using namespace sgt;
struct _
{
int x,l,r,v,id;
bool operator < (const _ &a) const {return x<a.x;}
}q[maxn];
int tot;
struct __
{
int x,l,r,v;
bool operator < (const __ &a) const {return x<a.x;}
}t[maxn];
int tt;
int ans[maxn];
signed main()
{
n=read(),m=read(),p1=read(),p2=read();
for (int i=1;i<=n;i++) a[i]=read();
stk[top=0]=0;
for (int i=1;i<=n;i++)
{
while (top && a[i]>a[stk[top]]) top--;
L[i]=stk[top],stk[++top]=i;
}
stk[top=0]=n+1;
for (int i=n;i>=1;i--)
{
while (top && a[i]>a[stk[top]]) top--;
// cerr << top << endl;
R[i]=stk[top],stk[++top]=i;
}
// cerr << "*************************" << endl;
// for (int i=1;i<=n;i++)
// cerr << L[i] << " ";
// cerr << endl;
// for (int i=1;i<=n;i++)
// cerr << R[i] << " ";
// cerr << endl;
// cerr << "*************************" << endl;
for (int i=1;i<=m;i++)
{
int l=read(),r=read();
ans[i]=(r-l)*p1;
q[++tot]={r,l,r,1,i};
q[++tot]={l-1,l,r,-1,i};
}
sort(q+1,q+1+tot);
for (int i=1;i<=n;i++)
{
if (L[i] && R[i]<=n)
t[++tt]={R[i],L[i],L[i],p1};
if (L[i] && R[i]-1>=i+1)
t[++tt]={L[i],i+1,R[i]-1,p2};
if (L[i]+1<=i-1 && R[i]<=n)
t[++tt]={R[i],L[i]+1,i-1,p2};
}
sort(t+1,t+1+tt);
// for (int i=1;i<=tot;i++)
// cerr << q[i].x << " " << q[i].l << " "
// << q[i].r << " " << q[i].v << " "
// << q[i].id << endl;
// for (int i=1;i<=tt;i++)
// cerr << t[i].x << " " << t[i].l << " "
// << t[i].r << " " << t[i].v << " " << endl;
for (int i=1,j=1;i<=tot;i++)
{
while (j<=tt && t[j].x<=q[i].x)
Upd(1,0,n+1,t[j].l,t[j].r,t[j].v),j++;
ans[q[i].id]+=que(1,0,n+1,q[i].l,q[i].r)*q[i].v;
}
for (int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
P5070 [Ynoi Easy Round 2015] 即便看不到未来
- 珂学好题
虽然但是msjing不会做呢- sbmqwm:不是珂学家导致的
- 参考题解
sbmqwm:查重率好高呦- 看题,经过一阵晁吉雕的操作,让求长不超过 \(10\) 的极长连续段个数,说白了就是让序列不能再通过 \(+1\) 或 \(-1\) 操作延长
- 本质不好搞,看看数据范围,\(10^6\),时限开到 \(3s\),大概正解是 \(O(n \log_n)\) 的,并不要求在线,直接启动扫描线
- 还是传统扫描线做法,以右端点为关键字排序(msjing新学了用 \(vector\) 的维护法),然后开扫
- 那么考虑新加一个数 \(a_i\) 对答案的影响,
我们发现在 \(a_i\) 这个数出现的上一个位置之前的所有数都与此次加入无关(e,貌似很好理解?),我们令上一次 \(a_i\) 出现的位置为 \(last_{a_i}\)(代码里为 \(lt\)),那么贡献区间就是 \((last_{a_i} , i]\) - 我们进一步考虑,由于我们只需要求长度不超过 \(10\) 的,所以本质上只需要管 \([a_i-10 , a_i+10]\) 的区间,把 \(last\) 从小到大排序,倒着扫,令 \(x\) 所在连续段最大值和最小值分别为 \(L\) 和 \(R\),会有三种情况
- \(L = x\),即 \(x\) 是开头,把 \(R - x\) 的贡献减去,加上 \(R - x + 1\) 的贡献,其中的 \(x\) 可以换成 \(L\)
- \(R = x\),即 \(x\) 是末尾,同理
- $ L < x < R$,即 \(x\) 连接了两个连续段,两端原长 \(x - L\) 和 \(R - x\),新长为 \(R - L + 1\)
- 发现相似了没?上述三种都是减去\(x - L\) 和 \(R - x\) 并加上 \(R - L + 1\)
- 令现在扫到的下标为 \(j\),最后一次出现这个数的位置为 \(pos_j\),修改的影响区间就是 \([pos_{j-1}+1 , pos_j]\)
- 这是一个区间的操作,并需要单点查,
考虑到是 Ynoi 会卡常,所以用树状数组 - 边界要注意,在减掉 \([a_i-10 , a_i+10]\) 的旧贡献时要把 \([a_i-11 , a_i+11]\) 的贡献也算进去,这是因为在算 \([a_i-10 , a_i+10]\) 的贡献时需要 \([a_i-11 , a_i+11]\) 的贡献
- 时间复杂度大概是 \(O(n \log_n)\) 的,带一个 \(10\) 的常数(要求 \(10\) 个数),轻微卡常,
毕竟是 Ynoi - e,代码基本是贺的,没啥可看的,就是若之msjing把 \(n\) 定义为全局变量后又在主函数定义了一遍然后树状数组的 \(n\) 就成 \(0\) 了
点击查看代码
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
constexpr int maxn=1e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,a[maxn],k;
struct _ {int l,r,id;};
vector<_> q[maxn];
struct BIT
{
int c[maxn];
int lowbit(int x) {return x&(-x);}
void upd(int x,int v) {while (x<=n) c[x]+=v,x+=lowbit(x);}
void add(int x,int y,int v) {upd(x,v),upd(y+1,-v);}
int getsum(int x)
{
int res=0;
while(x) res+=c[x],x-=lowbit(x);
return res%10;
}
}tr[12];
struct __ {int p,x;}t[maxn];
bool cmp1(__ a,__ b) {return a.p<b.p;}
int tot;
int v[maxn],lt[maxn],ans[maxn][12];
int main()
{
n=read(),m=read();for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=m;i++)
{
int l=read(),r=read();
q[r].pb({l,r,i});
}
int mx=1e6;
for (int i=1;i<=n;i++)
{
tot=0;
int L=a[i],R=a[i];
t[++tot]={i,a[i]};
for (int j=max(1,a[i]-11);j<=min(mx,a[i]+11);j++) v[j]=0;
for (int j=max(1,a[i]-11);j<=min(mx,a[i]+11);j++) if (lt[j]) t[++tot]={lt[j],j};
sort(t+1,t+1+tot,cmp1);
for (int j=tot;j && t[j].p>lt[a[i]];j--)
{
v[t[j].x]=1;
while (v[L-1] && L-1>=max(1,a[i]-11)) L--;
while (v[R+1] && R+1<=min(mx,a[i]+11)) R++;
tr[a[i]-L].add(t[j-1].p+1,t[j].p,-1);
tr[R-a[i]].add(t[j-1].p+1,t[j].p,-1);
if (R-L+1<=10) tr[R-L+1].add(t[j-1].p+1,t[j].p,1);
}
lt[a[i]]=i;
for (auto k:q[i]) for (int j=1;j<=10;j++) ans[k.id][j]=tr[j].getsum(k.l);
}
for (int i=1;i<=m;i++)
{
for (int j=1;j<=10;j++) printf("%d",ans[i][j]);
putchar('\n');
}
return 0;
}
P8512 [Ynoi Easy Round 2021] TEST_152
- 前置芝士:珂朵莉树
- 这里看
P9991 [Ynoi Easy Round 2023] TEST_107
- 卡过了拜谢神犇QED/bx


- 这道题让求最长子序列令其中元素个数比原序列小
- 我们发现这个东西不好做,所以我们转化一下
- 我们现在考虑从区间中删除元素(删干净)
- 我们令这个要删掉的数为 \(k\),那么这个数会有三种出现的相邻位置(就是这俩数之间没有与这俩数相同的数了)
- \(l < k_1 < k_2 < r\),即在序列中间
- \(k_1 < l < k_2 < r\),即在序列左侧和中间
- \(l < k_1 < r < k_2\),即在序列中间和右侧
- 三种区间分别为 \((k_1,k_2)\),\([l,k_2)\),\((k_1,r]\)
- 维护一个区间数出现的次数,且不要求在线,所以直接扫描线
- 维护一个线段树,支持单点修区间查
- 第一种:设扫到了 \(i\),在上一次这个数出现的位置(记为 \(pre\)),改成 \(i-pre_i-1\),查就是从 \(i\) 到 \(q_r\)
- 第二种:如果 \(i\) 的前驱不存在,把 \(i\) 位置修成 \(i\),在 \(i\) 的下一次出现的位置(记为 \(nxt\)),改为 \(nxt\),查就是 \(i\) 到 \(k_r\),注意先查后改
- 第三种:和第二种类似,不过要倒着扫
- 其实代码清晰,看代码就能懂
- 然后你就愉快地
AC被卡了
lxl orz!!!!!
先放个原代码
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
constexpr int maxn=2e6+10,inf=0x3f3f3f3f;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
int a[maxn];
int lt[maxn],pre[maxn],nxt[maxn];
vector<pii > ql[maxn],qr[maxn];
int ans[maxn];
namespace sgt
{
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((l+r) >> 1)
int mx[maxn<<2];
void Pus(int rt) {mx[rt]=max(mx[ls],mx[rs]);}
void Build(int rt,int l,int r)
{
mx[rt]=-inf;
if (l == r) return;
Build(ls,l,mid),Build(rs,mid+1,r);
}
void Upd(int rt,int l,int r,int pos,int v)
{
if (l == r && l == pos) return mx[rt]=v,void();
if (pos<=mid) Upd(ls,l,mid,pos,v);
else Upd(rs,mid+1,r,pos,v);
Pus(rt);
}
int que(int rt,int l,int r,int L,int R)
{
int res=-inf;
if (L<=l && r<=R) return mx[rt];
if (L<=mid) res=max(res,que(ls,l,mid,L,R));
if (R>mid) res=max(res,que(rs,mid+1,r,L,R));
return res;
}
#undef ls
#undef rs
#undef mid
}using namespace sgt;
int main()
{
// freopen("1.in","r",stdin);
n=read(),m=read();
for (int i=1;i<=n;i++)
a[i]=read(),pre[i]=lt[a[i]],lt[a[i]]=i,nxt[pre[i]]=i;
for (int i=1;i<=m;i++)
{
int l=read(),r=read();
qr[r].pb({l,i}),ql[l].pb({r,i});
}
Build(1,1,n);
for (int i=1;i<=n;i++)
{
if (pre[i])
Upd(1,1,n,pre[i],i-pre[i]-1);
for (pii k:qr[i])
ans[k.se]=max(ans[k.se],que(1,1,n,k.fi,i));
}
Build(1,1,n);
for (int i=1;i<=n;i++)
if (!pre[i])
Upd(1,1,n,i,i);
for (int i=1;i<=n;i++)
{
for (pii k:ql[i])
{
if (i == k.fi) continue;
ans[k.se]=max(ans[k.se],que(1,1,n,i,k.fi)-i);
}
if (nxt[i])
Upd(1,1,n,nxt[i],nxt[i]);
Upd(1,1,n,i,-inf);
}
Build(1,1,n);
for (int i=1;i<=n;i++)
if (!nxt[i])
Upd(1,1,n,i,-i);
for (int i=n;i>=1;i--)
{
for (pii k:qr[i])
{
if (i == k.fi) continue;
ans[k.se]=max(ans[k.se],que(1,1,n,k.fi,i)+i);
}
if (pre[i])
Upd(1,1,n,pre[i],-pre[i]);
Upd(1,1,n,i,-inf);
}
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
- 我们发现线段树在 Ynoi 里常数完全不能忽略,所以我们必须开始卡常
- 再次拜谢 QED 大蛇
- 介绍两个卡常利器
递归展开
- 这个好像听起来挺吊,其实挺简单
- 我们想想 \(Upd\) 的原理,先是通过 \(rt<<1\) 和 \(rt<<1|1\) 向下到左右儿子,再继续向下到儿子,直到叶子节点,更新,上传
- 所以我们手动模拟向下找叶子的过程,没啥东西,和递归差不多
while (1)
{
if (l == r)
{
mx[rt]=v;
break;
}
if (pos<=mid) rt=ls,r=mid;
else rt=rs,l=mid+1;
}
- 然后我们考虑回溯,我们知道节点编号的发放原理,我们直接反着来,每次让 \(rt>>=1\)
while (1)
{
rt>>=1;
mx[rt]=max(mx[ls],mx[rs]);
if (rt == 1) break;
}
- e,对了,貌似区间操作也可以,但是貌似要用栈也没必要
查询改动
- e,这个要不直接粘 QED 给我发的?
- 内个,QED 本人要是不想展示给msjing说喵,msjing立即删喵

- 和上面对比一下查询就知道了
- 还卡不过怎么办
- sbmqwm:用神威·太湖之光评测
- 有以下解决方法:
- 循环展开(貌似这题要展开 \(2 \times 10^6\)?且好像展开了交不了?)
- 超级快读
超级快读
struct IO
{
static const int Size=(1<<18);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
inline void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
inline IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>inline IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
inline IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
inline IO& operator <<(const char c){pc(c);return *this;}
template<typename T> inline IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
inline IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
inline IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
- 来源是LZY
- 用法和 \(cin\)、\(cout\) 一样,就是改成 \(fin\)、\(fout\),支持
'\n' - 那个 \(Size\) 是缓冲区(?),一般放 \(1<<21\),但是这题我亲测大该是 \(1<<18\) 最优
- 开火车
火车头
#pragma G++ optimize(1)
#pragma G++ optimize(2)
#pragma G++ optimize(3)
#pragma G++ optimize("Ofast")
#pragma G++ optimize("inline")
#pragma G++ optimize("-fgcse")
#pragma G++ optimize("-fgcse-lm")
#pragma G++ optimize("-fipa-sra")
#pragma G++ optimize("-ftree-pre")
#pragma G++ optimize("-ftree-vrp")
#pragma G++ optimize("-fpeephole2")
#pragma G++ optimize("-ffast-math")
#pragma G++ optimize("-fsched-spec")
#pragma G++ optimize("unroll-loops")
#pragma G++ optimize("-falign-jumps")
#pragma G++ optimize("-falign-loops")
#pragma G++ optimize("-falign-labels")
#pragma G++ optimize("-fdevirtualize")
#pragma G++ optimize("-fcaller-saves")
#pragma G++ optimize("-fcrossjumping")
#pragma G++ optimize("-fthread-jumps")
#pragma G++ optimize("-funroll-loops")
#pragma G++ optimize("-fwhole-program")
#pragma G++ optimize("-freorder-blocks")
#pragma G++ optimize("-fschedule-insns")
#pragma G++ optimize("inline-functions")
#pragma G++ optimize("-ftree-tail-merge")
#pragma G++ optimize("-fschedule-insns2")
#pragma G++ optimize("-fstrict-aliasing")
#pragma G++ optimize("-fstrict-overflow")
#pragma G++ optimize("-falign-functions")
#pragma G++ optimize("-fcse-skip-blocks")
#pragma G++ optimize("-fcse-follow-jumps")
#pragma G++ optimize("-fsched-interblock")
#pragma G++ optimize("-fpartial-inlining")
#pragma G++ optimize("no-stack-protector")
#pragma G++ optimize("-freorder-functions")
#pragma G++ optimize("-findirect-inlining")
#pragma G++ optimize("-frerun-cse-after-loop")
#pragma G++ optimize("inline-small-functions")
#pragma G++ optimize("-finline-small-functions")
#pragma G++ optimize("-ftree-switch-conversion")
#pragma G++ optimize("-foptimize-sibling-calls")
#pragma G++ optimize("-fexpensive-optimizations")
#pragma G++ optimize("-funsafe-loop-optimizations")
#pragma G++ optimize("inline-functions-called-once")
#pragma G++ optimize("-fdelete-null-pointer-checks")
#pragma G++ optimize("Ofast,no-stack-protector")
#pragma G++ target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
- luogu只支持 \(G++\) 哦
- 求仙问道(比如我问 QED)(诶我怎么打不对 QED 了)
- 进行神秘仪式(比如高呼作法)
- 找个人少的时间点(比如早上六点),评测姬比较闲
点击查看代码
#pragma G++ optimize(1)
#pragma G++ optimize(2)
#pragma G++ optimize(3)
#pragma G++ optimize("Ofast")
#pragma G++ optimize("inline")
#pragma G++ optimize("-fgcse")
#pragma G++ optimize("-fgcse-lm")
#pragma G++ optimize("-fipa-sra")
#pragma G++ optimize("-ftree-pre")
#pragma G++ optimize("-ftree-vrp")
#pragma G++ optimize("-fpeephole2")
#pragma G++ optimize("-ffast-math")
#pragma G++ optimize("-fsched-spec")
#pragma G++ optimize("unroll-loops")
#pragma G++ optimize("-falign-jumps")
#pragma G++ optimize("-falign-loops")
#pragma G++ optimize("-falign-labels")
#pragma G++ optimize("-fdevirtualize")
#pragma G++ optimize("-fcaller-saves")
#pragma G++ optimize("-fcrossjumping")
#pragma G++ optimize("-fthread-jumps")
#pragma G++ optimize("-funroll-loops")
#pragma G++ optimize("-fwhole-program")
#pragma G++ optimize("-freorder-blocks")
#pragma G++ optimize("-fschedule-insns")
#pragma G++ optimize("inline-functions")
#pragma G++ optimize("-ftree-tail-merge")
#pragma G++ optimize("-fschedule-insns2")
#pragma G++ optimize("-fstrict-aliasing")
#pragma G++ optimize("-fstrict-overflow")
#pragma G++ optimize("-falign-functions")
#pragma G++ optimize("-fcse-skip-blocks")
#pragma G++ optimize("-fcse-follow-jumps")
#pragma G++ optimize("-fsched-interblock")
#pragma G++ optimize("-fpartial-inlining")
#pragma G++ optimize("no-stack-protector")
#pragma G++ optimize("-freorder-functions")
#pragma G++ optimize("-findirect-inlining")
#pragma G++ optimize("-frerun-cse-after-loop")
#pragma G++ optimize("inline-small-functions")
#pragma G++ optimize("-finline-small-functions")
#pragma G++ optimize("-ftree-switch-conversion")
#pragma G++ optimize("-foptimize-sibling-calls")
#pragma G++ optimize("-fexpensive-optimizations")
#pragma G++ optimize("-funsafe-loop-optimizations")
#pragma G++ optimize("inline-functions-called-once")
#pragma G++ optimize("-fdelete-null-pointer-checks")
#pragma G++ optimize("Ofast,no-stack-protector")
#pragma G++ target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define getchar getchar_unlocked
#define putchar putchar_unlocked
// #define max(a,b) ((a)>(b)?(a):(b))
// #define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
constexpr int maxn=2e6+10,inf=0x7f7f7f7f;
struct IO
{
static const int Size=(1<<18);
char buf[Size],*p1,*p2;
int st[105],Top;
~IO(){clear();}
inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
inline void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
inline IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
template<typename T>inline IO& operator >>(T& x)
{
x=0;bool f=0;char c=gc();
while(!isdigit(c)){if(c=='-') f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
f?x=-x:0;
return *this;
}
inline IO& operator >>(string& s)
{
s="";char c=gc();
while(c==' ' || c=='\n' || c=='\r') c=gc();
while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
return *this;
}
inline IO& operator <<(const char c){pc(c);return *this;}
template<typename T> inline IO& operator <<(T x)
{
if(x<0) pc('-'),x=-x;
do st[++st[0]]=x%10,x/=10;while(x);
while(st[0]) pc(st[st[0]--]+'0');
return *this;
}
inline IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
inline IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
int n,m;
int a[maxn];
int lt[maxn],pre[maxn],nxt[maxn];
vector<pii > ql[maxn],qr[maxn];
int ans[maxn];
int l,r,i;
namespace sgt
{
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((l+r) >> 1)
int mx[maxn<<2];
inline void Upd(int rt,int l,int r,int pos,int v)
{
while (1)
{
if (l == r)
{
mx[rt]=v;
break;
}
if (pos<=mid) rt=ls,r=mid;
else rt=rs,l=mid+1;
}
while (1)
{
rt>>=1;
mx[rt]=max(mx[ls],mx[rs]);
if (rt == 1) break;
}
}
inline int que(int rt,int l,int r,int L,int R)
{
int res=-inf;
if (L<=l && r<=R) return mx[rt];
if (L<=mid) res=max(res,que(ls,l,mid,L,R));
if (R>mid) res=max(res,que(rs,mid+1,r,L,R));
return res;
}
#undef ls
#undef rs
#undef mid
}using namespace sgt;
signed main()
{
// freopen("P9991.in","r",stdin);
fin >> n >> m;
for (i=1;i<=n;i++)
fin >> a[i],pre[i]=lt[a[i]],lt[a[i]]=i,nxt[pre[i]]=i;
for (i=1;i<=m;i++)
{
fin >> l >> r;
qr[r].pb({l,i}),ql[l].pb({r,i});
}
for (i=1;i<=(n<<2);i++) mx[i]=-inf;
for (i=1;i<=n;i++)
{
if (pre[i])
Upd(1,1,n,pre[i],i-pre[i]-1);
for (pii k:qr[i])
ans[k.se]=max(ans[k.se],que(1,1,n,k.fi,n));
Upd(1,1,n,i,-inf);
}
for (i=1;i<=(n<<2);i++) mx[i]=-inf;
for (i=1;i<=n;i++)
if (!pre[i])
Upd(1,1,n,i,i);
for (i=1;i<=n;i++)
{
for (pii k:ql[i])
{
if (i == k.fi) continue;
ans[k.se]=max(ans[k.se],que(1,1,n,1,k.fi)-i);
}
if (nxt[i])
Upd(1,1,n,nxt[i],nxt[i]);
Upd(1,1,n,i,-inf);
}
for (i=1;i<=(n<<2);i++) mx[i]=-inf;
for (i=1;i<=n;i++)
if (!nxt[i])
Upd(1,1,n,i,-i);
for (i=n;i>=1;i--)
{
for (pii k:qr[i])
{
if (i == k.fi) continue;
ans[k.se]=max(ans[k.se],que(1,1,n,k.fi,n)+i);
}
if (pre[i])
Upd(1,1,n,pre[i],-pre[i]);
Upd(1,1,n,i,-inf);
}
for (i=1;i<=m;i++)
fout << ans[i] << '\n';
return 0;
}
我会卡常!



后话
- 这次再学习好像非常成功,但好像好多 \(trick\) 都没 \(get\) 到(·W·?)
- 总之扫描线的再学习就结束了,
万一有什么再再学习,没了 - 没有小剧场,走吧(逃

浙公网安备 33010602011771号