扫描线再学习

前言

  • 东西有点多渲染有点慢
  • 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\) 捏造:老传统了不用管


  • 其实还可以用 \(map\) 维护,但是msjing不会写,就撂着了,咕咕
  • 普通应用就到这了,可以去道板子毒瘤板子

周长

  • 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;
}

二维数点

给一个长为 \(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\)

111

  • 为什么 \(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\),会有三种情况
    1. \(L = x\),即 \(x\) 是开头,把 \(R - x\) 的贡献减去,加上 \(R - x + 1\) 的贡献,其中的 \(x\) 可以换成 \(L\)
    2. \(R = x\),即 \(x\) 是末尾,同理
    3. $ 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

r16obpqh

2026-07-27 20-27-14屏幕截图

  • 这道题让求最长子序列令其中元素个数比原序列小
  • 我们发现这个东西不好做,所以我们转化一下
  • 我们现在考虑从区间中删除元素(删干净)
  • 我们令这个要删掉的数为 \(k\),那么这个数会有三种出现的相邻位置(就是这俩数之间没有与这俩数相同的数了)
  1. \(l < k_1 < k_2 < r\),即在序列中间
  2. \(k_1 < l < k_2 < r\),即在序列左侧和中间
  3. \(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立即删喵

2026-08-01 20-53-46屏幕截图

  • 和上面对比一下查询就知道了
  • 还卡不过怎么办
  • sbmqwm:用神威·太湖之光评测
  • 有以下解决方法:
  1. 循环展开(貌似这题要展开 \(2 \times 10^6\)?且好像展开了交不了?)
  2. 超级快读
超级快读
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\) 最优
  1. 开火车
火车头
#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++\)
  1. 求仙问道(比如我问 QED)(诶我怎么打不对 QED 了)
  2. 进行神秘仪式(比如高呼作法)
  3. 找个人少的时间点(比如早上六点),评测姬比较闲
点击查看代码
#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;
}
我会卡常!

2026-08-01 21-07-45屏幕截图

2026-08-01 21-07-35屏幕截图

2026-08-01 21-07-12屏幕截图

后话

  • 这次再学习好像非常成功,但好像好多 \(trick\) 都没 \(get\) 到(·W·?)
  • 总之扫描线的再学习就结束了,万一有什么再再学习,没了
  • 没有小剧场,走吧(逃
posted @ 2026-07-19 11:32  msjing  阅读(53)  评论(12)    收藏  举报