标签:++ i+1 连续 参考资料 映射 VID swa using art
A组的题实在是太与时俱进了……
这是LCAdalao在今年的WC上提出来的算法(也是数据结构),用途是维护一类关于连续段的计数问题。
不动点(连续段):对于排列\(P\),定义连续段\((P,[l,r])\)表示一个段,要求\(P_{l\sim r}\)值域是连续的。形式化地说,对于排列\(P\),连续段是一个段\(Q\)满足\(Q\in I∧P[Q]\in I\)。在以后的叙述中,我们用\(I_P\)表示所有连续段的集合。一个连续段的值域\(ran(S)=[\min_{i\in S}P_i,\max_{i\in S}P_i]\)。
那又有一个性质:一个连续段可以由几个本原连续段构成。这样一来,本原连续段的集合\(M_P\)就是\(I_P\)的一个极小基(即不存在比它更小却能表示出\(I_P\)的集合)。
析点和合点也有一些显然的性质:对于非叶节点,合点至少有两个儿子,析点至少有四个……
考虑如何加入一个点。
然后又由于走过的\(L_i\)不会再走了,故而构造的时间复杂度是\(O(n)\)的。
这两道题都是求包含询问区间的最小连续段,那么就是在析合树上找LCA,假如找到的是析点则直接取析点;不然的话,要从合点LCA下移一步走到两个点,然后取两个点的并(因为合点可能包括了其他不在询问范围内的本原连续段)。
#include <cstdio>
#include <algorithm>
#define A v*2
#define B A+1
#define min(x,y) (x<y?x:y)
#define max(x,y) (x>y?x:y)
#define MIN(x,y) if(x>y)x=y
#define MAX(x,y) if(x<y)x=y
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=233333;
int n,a[N],pos[N],t0[N*4],t1[N*4],pl,pr,id[N];
void bt(int v,int l,int r)
{
if(l==r) {t0[v]=t1[v]=pos[l]; return;}
int m=l+r>>1;
bt(A,l,m), bt(B,m+1,r);
t0[v]=min(t0[A],t0[B]);
t1[v]=max(t1[A],t1[B]);
}
struct P
{
int x,y;
inline P(){x=N,y=0;}
inline P(int _x,int _y){x=_x,y=_y;}
};
inline void uni(P&a,const P&b) {MIN(a.x,b.x); MAX(a.y,b.y);}
struct nod
{
bool dv;
int ls;
P a,b,c,d;
}b[N]; int b0;
void ft(int v,int l,int r)
{
if(pr< l||r< pl) return;
if(pl<=l&&r<=pr) {MIN(b[b0].c.x,t0[v]); MAX(b[b0].c.y,t1[v]); return;}
int m=l+r>>1;
ft(A,l,m), ft(B,m+1,r);
}
inline bool ck(nod a) {return a.a.y-a.a.x==a.b.y-a.b.x;}
inline nod uni(nod a,nod b)
{
uni(a.a,b.a), uni(a.b,b.b);
a.d=a.c, uni(a.d,b.d);
uni(a.c,b.c);
return a;
}
int z[N],z0,f[N][17],r[N],q0,q[N],dep[N];
struct fail{int x; nod a;}d[N];
int add(int x)
{
if(!z0) {z[++z0]=x; return 0;}
int y=z[z0];
if(!b[y].dv&&ck(uni(b[b[y].ls],b[x])))
{
b[y]=uni(b[y],b[x]), z0--;
return f[b[y].ls=x][0]=y;
}
if(ck(uni(b[y],b[x])))
{
b[++b0]=uni(b[y],b[x]), z0--;
b[b0].ls=x, b[b0].dv=0;
return f[y][0]=f[x][0]=b0;
}
int t=z0;
nod e=uni(b[y],b[x]);
for(; e.d.y<=b[x].a.y&&!ck(e); t=d[t].x) e=uni(d[t].a,e);
if(ck(e))
{
b[++b0]=e, b[b0].dv=1, b[b0].ls=z[++z0]=x;
fo(i,t,z0) f[z[i]][0]=b0;
z0=t-1;
return b0;
}
z[++z0]=x;
d[z0]=(fail){t,e};
return 0;
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
fd(i,16,0) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
fd(i,16,0) if(f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int go(int x,int y)
{
fd(i,16,0) if(dep[f[y][i]]>dep[x]) y=f[y][i];
return y;
}
int m,x,y;
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]), pos[a[i]]=i;
bt(1,1,n);
fo(i,1,n)
{
b[id[i]=++b0].dv=1;
b[b0].a=P(i,i);
b[b0].b=P(a[i],a[i]);
b[b0].c=b[b0].d=P();
if(i<n)
{
pl=a[i], pr=a[i+1];
if(pl>pr) swap(pl,pr);
ft(1,1,n);
}
for(x=b0; x; x=add(x));
}
fo(i,1,b0) r[f[i][0]]++;
fo(i,1,b0) if(!r[i]) q[++q0]=i;
fo(i,1,q0)
{
x=q[i];
if(f[x][0]&&!--r[f[x][0]]) q[++q0]=f[x][0];
}
fd(i,q0,1)
{
x=q[i];
dep[x]=dep[f[x][0]]+1;
fo(j,1,16) f[x][j]=f[f[x][j-1]][j-1];
}
for(scanf("%d",&m); m--;)
{
scanf("%d%d",&x,&y);
int z=lca(id[x],id[y]);
nod w=b[z].dv ? b[z] : uni(b[go(z,id[x])],b[go(z,id[y])]);
printf("%d %d\n",w.a.x,w.a.y);
}
}
简单的连续段数据结构by LCAdalao
析合树学习小记by cc
析合树 - OI Wiki
标签:++ i+1 连续 参考资料 映射 VID swa using art
原文地址:https://www.cnblogs.com/Iking123/p/11309119.html