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();
}
本文来自博客园,作者:wing_heart,转载请注明原文链接:https://www.cnblogs.com/wingheart/p/18709791