P7907 [Ynoi2005] rmscne/becoder 青蛙题

P7907 [Ynoi2005] rmscne/becoder 青蛙题

题意

给定长为 \(n\) 的序列,\(q\) 次询问区间 \([l,r]\) 的最短子区间 \([l',r']\),满足所有在 \([l,r]\) 中出现的数也在 \([l',r']\) 中出现. 你只需要输出 \([l',r']\) 的长度 \(r'-l'+1\)

对于 \(100\%\) 的数据,满足 \(1\leq n,q\leq 2\times 10^6\)\(1\leq a_i\leq n\)

思路

我觉得扫描线问题和数点问题有共通的地方,我想试着把扫描线问题套成数点问题。


定义 \(S_{l,r}\) 表示 \([l,r]\) 的数的集合,题意即求满足条件 \(S_{l,r} = S_{l',r'}\) 的最小 \(r'-l'+1\)

容易想到 \(O(n^2)\) 的双指针暴力:在线处理询问,枚举 \(l'\),容易发现 \(r_i\) 随着 \(l'\) 的增大单调不降。


换一下变量名字,\(q\) 次询问 \([l_i,r_i]\),求长度最小的合法的 \([x,y]\)

对于询问 \(i\) 的答案 \([x,y]\),不难证明等同于后缀 \(x\)\([x,r_i]\))最小合法(\(S_{x,r_i} = S_{x,y}\))前缀 \(y\)\([x,y]\))。

因此我们可以对每个询问区间求每个后缀的最小合法前缀,然后取 \(\min\)

题解的做法是扫描右端点 \(p\)。数据结构维护右端点为 \(p\) 时每个下标 \(k\) 代表的后缀的最小合法前缀 \(f_k\)

对于 \(y=p\),找出 \(a_p\) 上一次出现的位置 \(la_p\),那么询问的 \(l\) 落在区间 \((la_p,p]\) 的询问的答案区间都要包含 \(p\) 才合法。把这个区间的 \(f\) 都对 \(p\)\(\max\) 即可。

对于询问 \(r_i=p\),答案是最大的数据左端点 \(d\) 满足这个后缀合法,答案取 \(\min_{x=l}^d p_x\)

找这个 \(d\),在加入 \(p\) 的时候,\(la_p\) 就可以不需要包括了,因此使用并查集让 \(la_p\) 指向 \(la_p +1\)\(d\) 就是从 \(l\) 一直跳并查集,就可以跳到最小的必须要包含的点了。

我觉得这个做法很难想,主要是因为你想到一个思路时无法判定这个思路是否是有前途的。所以把问题套成偏序问题的表述方式可能会有帮助?


类比二维偏序问题。

\(O(n^2)\) 个数据 \([x_j,y_j], 1 \le x \le n, x \le y \le n\)。有 \(q\) 个询问 \([l_i,r_i]\)

原始问题是每组 \((x,y,l,r)\) 都有一个权值 \([S_{x,y} = S_{l,r}]\)。对每个询问求二维偏序的点的权值之和。

但是这里的权值与数据和询问均有关,没法做。

发现问题可以转化成每个后缀的最大合法前缀。于是可以转化成询问就是询问 \(O(n)\) 个后缀点对的权值,维护每个后缀点对的权值。

设每个后缀(一个数据点对)\((x,y)\) 的权值为它的最大合法前缀长度,发现第二维增加的时候,按照第一维维护权值是好维护的。

查询就是询问后缀的数据点对的权值。

总结一下,这里的数据点有 \(O(n^2)\) 个,询问点有 \(q\) 个,因此我们不得不使用数据结构维护数据的权值,遇到询问时在数据结构查询。(而不是采用用数据结构维护询问的答案的方式)

我们发现扫描线的时候数据点的权值是好维护的(最简单的二维偏序问题,数据点的权值就是 \(1\))。查询只需要对满足偏序的数据求权值和。

这里我们的偏序是一维偏序,即 \(l_i \le x_j \le r_i, y_j = r_i\),第二维没有偏序关系。

如果只是一维偏序的话,你完全可以按照任意顺序枚举第二维。但是本题数据点权值的维护必须按照顺序来维护,这就是为什么本题必须按照第二维的一定顺序扫描了。

code

被卡常了,只能改 zkw 线段树了。诶,我是不是应该开个快读就可以了吧。

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
	#define isdigit(x) (x>='0' && x<='9') 
	#define gc getchar_unlocked
	#define pc putchar_unlocked
	void read(int &x) {
		x=0;
		char ch=gc();
		for(;!isdigit(ch);ch=gc());
		for(;isdigit(ch);ch=gc()) x=(x<<3) + (x<<1) + (ch^48);
	}
	void write(int x,char ch) {
		static int st[20];
		int top=0;
		do {
			st[top++]=x%10;
			x/=10;
		} while(x);
		while(top) pc(st[--top]^48);
		pc(ch);
	}
	constexpr int N=2e6+7;
	int n,a[N],q,l[N],r[N];
	vector<int> que[N];
	int tr[N<<2],tg[N<<2];
	int la[N],fa[N];
	int mp[N];
	int find(int u) { return fa[u]==u ? u : fa[u]=find(fa[u]); }
	int ans[N];
	void maketag(int u,int r,int x) {
		tr[u]=x-r+1, tg[u]=x;
	}
	void pushdown(int u,int r,int mid) {
		if(tg[u]) maketag(u<<1,mid,tg[u]), maketag(u<<1|1,r,tg[u]), tg[u]=0;
	}
	void pushup(int u) {
		tr[u]=min(tr[u<<1],tr[u<<1|1]);
	}
	void change(int L,int R,int x,int u=1,int l=1,int r=n) {
		if(l>=L && r<=R) return maketag(u,r,x);
		int mid=(l+r)>>1;
		pushdown(u,r,mid);
		if(L<=mid) change(L,R,x,u<<1,l,mid);
		if(mid+1<=R) change(L,R,x,u<<1|1,mid+1,r);
		pushup(u);
	}
	int query(int L,int R,int u=1,int l=1,int r=n) {
		if(l>=L && r<=R) return tr[u];
		int mid=(l+r)>>1;
		pushdown(u,r,mid);
		int s=0x3f3f3f3f;
		if(L<=mid) s=query(L,R,u<<1,l,mid);
		if(mid+1<=R) s=min(s,query(L,R,u<<1|1,mid+1,r));
		pushup(u);
		return s;
	}
    void main() {
		read(n);
		rep(i,1,n) read(a[i]), la[i]=mp[a[i]], mp[a[i]]=i;
		read(q);
		rep(i,1,q) read(l[i]), read(r[i]), que[r[i]].push_back(i);
		memset(tr,0x3f,sizeof(tr));
		rep(p,1,n) {
			change(la[p]+1,p,p);
			fa[p]=p;
			fa[la[p]]=la[p]+1;
			for(int id : que[p]) {
				int L=l[id];
				int d=find(L);
				ans[id]=query(L,d);
			}
		}
		rep(i,1,q) write(ans[i],'\n');
    }
}
int main() {
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("my.out","w",stdout);
    #endif
    wing_heart :: main();
}
posted @ 2025-02-14 20:33  wing_heart  阅读(10)  评论(0)    收藏  举报