【NOI2019集训题2】 序列 后缀树+splay+dfs序
题目大意:给你一个长度为$n$的序列$a_i$,还有一个数字$m$,有$q$次询问
每次给出一个$d$和$k$,问你对所有的$a_i$都在模$m$意义下加了$d$后,第$k$小的后缀的起点编号。
数据范围:$n≤100000,d≤a_i<m≤10^9,q≤5\times 10^5$
这一题我想的时候被最后一步卡主了(其实如果到那个时候估计也时间不够了)
我们不难找出一个单次询问$O(n)$的方法,我们每次暴力更新$a_i$,然后对原序列搞一棵后缀树出来,在上面暴力查询第$k$小即可。
如果没有加$d$的操作,只是单纯询问第k大的话,我们考虑对这棵后缀树按字典序先序遍历一遍,搞出dfs序,用平衡树维护这些点出现的先后顺序。
对于一次询问第$k$小,我们在平衡树上找出dfs序第$k$小的后缀节点出来,输出即可。
下面考虑加$d$的操作。
考虑到这一题并没有要求强制在线,我们考虑对所有的询问按$d$从小大到大排序。
随着d的增长,原先最大的数随着取模操作的发生,会变成最小的数。
也就是说会出现一个换位的情况
就像这样
显然这个红点下面还会接很多个节点,但是不管怎么换位,以红点为根的子树内孩子的数量是不会变的。
我们只需要不断地做搬动节点的操作(在平衡树中,取出一个区间,并把这个区间插入到另一个区间中),并且动态维护dfs序列即可。
在询问离线后,我们不难发现每个节点至多只需要被搬动一次。
那么时间复杂度就变成愉快的O((n+q)\ log\ n)了。
然而我在想的时候,并没有往dfs序上想,sam也不熟练
注意细节!
#include<bits/stdc++.h>
#define M 200005
#define lc(x) ch[(x)][0]
#define rc(x) ch[(x)][1]
using namespace std; int dfn[M]={},low[M]={},t=; int n,m,q; namespace H{
int ch[M][]={},fa[M]={},root,siz[M]={},psiz[M]={},p[M]={},rec[M]={},use=; void pushup(int x){siz[x]=siz[lc(x)]+siz[rc(x)]+;psiz[x]=psiz[lc(x)]+psiz[rc(x)]+p[x];}
void rotate(int x,int &k){
int y=fa[x],z=fa[y],l,r;
l=(ch[y][]!=x); r=l^;
if(y==k) k=x;
else{
if(ch[z][]==y) ch[z][]=x;
else ch[z][]=x;
}
fa[x]=z; fa[y]=x; fa[ch[x][r]]=y;
ch[y][l]=ch[x][r]; ch[x][r]=y;
pushup(y); pushup(x);
}
inline void splay(int x,int &k){
while(x!=k){
int y=fa[x],z=fa[y];
if(y!=k){
if((ch[y][]==x)^(ch[z][]==y)) rotate(x,k);
else rotate(y,k);
}
rotate(x,k);
}
}
int build(int l,int r,int f){
if(l>r) return ;
int mid=(l+r)>>; fa[mid]=f;
lc(mid)=build(l,mid-,mid);
rc(mid)=build(mid+,r,mid);
pushup(mid);
return mid;
}
int find(int x,int k){
if(siz[lc(x)]>=k) return find(lc(x),k);
if(siz[lc(x)]+==k) return x;
return find(rc(x),k-siz[lc(x)]-);
}
int findp(int x,int k){
if(psiz[lc(x)]>=k) return findp(lc(x),k);
if(psiz[lc(x)]+p[x]==k) return x;
return findp(rc(x),k-psiz[lc(x)]-p[x]);
}
int getrank(int x){
splay(x,root);
return siz[lc(x)];
}
int split(int l,int r){
int x=find(root,l),y=find(root,r+);
splay(x,root); splay(y,ch[root][]);
int res=lc(y);
lc(y)=;
splay(y,root);
return res;
}
void ins(int k,int id){
int x=find(root,k+);
splay(x,root);
int y=rc(x);
while(lc(y)) y=lc(y);
lc(y)=id; fa[id]=y;
splay(lc(y),root);
}
void build(int nn){
root=build(,nn+,);
splay(,root);
lc()=nn+; siz[]++; siz[nn+]++;
}
}; int a[M]={};
map<int,int> mp; vector<int> vt[M]; namespace SAM{
map<int,int> ch[M],son[M]; int l[M],fa[M],last=,use=,cnt=;
int pos[M],ed[M],val[M],siz[M];
void exc(int c,int id){
int p=last,np=++use; l[np]=l[last]+; last=np;
pos[np]=ed[np]=id;
for(;p&&ch[p][c]==;p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else{
int q=ch[p][c];
if(l[p]+==l[q]) fa[np]=q;
else{
int nq=++use;
l[nq]=l[p]+; fa[nq]=fa[q];
fa[q]=fa[np]=nq; ed[nq]=ed[q];
ch[nq]=ch[q];
for(int j=p;ch[j][c]==q;j=fa[j]) ch[j][c]=nq;
}
}
} void build(){
for(int i=n;i;i--)
exc(a[i],i);
for(int i=;i<=use;i++){
val[i]=a[ed[i]+l[fa[i]]];
if(mp[val[i]]==) mp[val[i]]=++cnt;
vt[mp[val[i]]].push_back(i);
son[fa[i]][val[i]]=i;
}
} void dfs(int x){
dfn[x]=++t; siz[x]=;
H::rec[t]=x;
if(pos[x]) H::p[t]=;
for(map<int,int>::iterator it=son[x].begin();it!=son[x].end();it++){
dfs(it->second);
siz[x]+=siz[it->second];
}
low[x]=t;
}
void updata(int ID){
for(int i=;i<vt[ID].size();i++){
int id=vt[ID][i];
int F=fa[id];
int wei=H::getrank(dfn[F]);
int cutID=wei+siz[F]-siz[id];
int P=H::split(cutID,cutID+siz[id]-); H::ins(wei,P);
}
}
}; struct ask{
int d,k,id;
ask(){d=k=id=;}
void rd(int ID){id=ID; scanf("%d%d",&d,&k);}
friend bool operator <(ask a,ask b){return a.d<b.d;}
}Q[]; int ans[]={}; int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d%d%d",&n,&m,&q);
for(int i=;i<=n;i++) scanf("%d",a+i); SAM::build();
SAM::dfs();
H::build(SAM::use); for(int i=;i<=q;i++) Q[i].rd(i);
sort(Q+,Q+q+);
sort(a+,a+n+);
int r=unique(a+,a+n+)-a-,cnt=r; for(int i=;i<=q;i++){
while(r&&a[r]+Q[i].d>=m){
if(mp[a[r]])
SAM::updata(mp[a[r]]);
r--;
}
int ID=H::findp(H::root,Q[i].k);
ans[Q[i].id]=SAM::pos[H::rec[ID]];
}
for(int i=;i<=q;i++) printf("%d\n",ans[i]);
}
【NOI2019集训题2】 序列 后缀树+splay+dfs序的更多相关文章
- 树的dfs序 && 系统栈 && c++ rope
利用树的dfs序解决问题: 就是dfs的时候记录每个节点的进入时间和离开时间,这样一个完整的区间就是一颗完整的树,就转化成了区间维护的问题. 比如hdu3887 本质上是一个求子树和的问题 #incl ...
- [2]树的DFS序
定义: 树的DFS序就是在对树进行DFS的时候,对树的节点进行重新编号:DFS序有一个很强的性质: 一颗子树的所有节点在DFS序内是连续的一段, 利用这个性质我们可以解决很多问题. 代码: void ...
- CF877E Danil and a Part-time Job 线段树维护dfs序
\(\color{#0066ff}{题目描述}\) 有一棵 n 个点的树,根结点为 1 号点,每个点的权值都是 1 或 0 共有 m 次操作,操作分为两种 get 询问一个点 x 的子树里有多少个 1 ...
- BZOJ_3729_Gty的游戏_博弈论+splay+dfs序
BZOJ_3729_Gty的游戏_博弈论+splay+dfs序 Description 某一天gty在与他的妹子玩游戏. 妹子提出一个游戏,给定一棵有根树,每个节点有一些石子,每次可以将不多于L的石子 ...
- HDU4117 GRE WORDS(AC自动机+线段树维护fail树的dfs序)
Recently George is preparing for the Graduate Record Examinations (GRE for short). Obviously the mos ...
- SPOJ Query on a tree III (树剖(dfs序)+主席树 || Splay等平衡树)(询问点)
You are given a node-labeled rooted tree with n nodes. Define the query (x, k): Find the node whose ...
- 【BZOJ3991】寻宝游戏(虚树,DFS序,splay)
题意:求在树中从任意点开始,经过若干个关键点回到原点的最小距离 要求支持在线将某个点设置(取消)为关键点,以及询问答案 n,m<=100000 len[i]<=10^9 思路:显然是一个虚 ...
- BZOJ2780: [Spoj]8093 Sevenk Love Oimaster(广义后缀自动机,Parent树,Dfs序)
Description Oimaster and sevenk love each other. But recently,sevenk heard that a girl named ChuYuXu ...
- BZOJ3786: 星系探索 Splay+DFS序
题目大意:给你一个树,支持三种操作,子树加,点到根的路径和,改变某一个点的父亲. 分析: 看起来像一个大LCT,但是很显然,LCT做子树加我不太会啊... 那么,考虑更换一个点的父亲这个操作很有意思, ...
随机推荐
- idea的maven依赖本地jar
可以手动添加jar,但是idea手动添加jar时,有时候不行. 用maven依赖本地jar方法,感觉比较正规,不会因为自己忘记手动添加jar. 比如这个达梦数据库依赖 <dependency&g ...
- Intellij IDEA 打包jar的多种方式
IDEA打包jar包的多种方式 用IDEA自带的打包形式 用Maven插件maven-shade-plugin打包 用Maven插件maven-assembly-plugin打包 1.view-> ...
- 【leetcode算法-简单】7.整数反转
[题目描述] 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123输出: 321 示例 2: 输入: -123输出: -321 示例 3: 输入: 12 ...
- 洛谷P4145——上帝造题的七分钟2 / 花神游历各国
题目背景 XLk觉得<上帝造题的七分钟>不太过瘾,于是有了第二部. 题目描述 "第一分钟,X说,要有数列,于是便给定了一个正整数数列. 第二分钟,L说,要能修改,于是便有了对一段 ...
- PAT(B) 1063 计算谱半径(Java)
题目链接:1063 计算谱半径 (20 point(s)) 题目描述 在数学中,矩阵的"谱半径"是指其特征值的模集合的上确界.换言之,对于给定的 n 个复数空间的特征值 { a1 ...
- 基于TCP的编程
前提:本文基于Linux系统下的学习 服务器端 1 创建通讯端口,返回socket设备的文件描述符 sfdsocket(2)#include <sys/types.h> /* See NO ...
- SPI时序
1.串行外围接口 高速.全双工的同步通信总线 一主多从 一般速度几十MHZ,最高可以工作在上百MHZ 2.连接图 3.工作模式
- Ubuntu中更改默认的root用户密码,以及怎样修改用户密码
新安装的Ubuntu系统中默认的root用户密码是多少?该怎么修改? 如题,相信许多刚接触Ubuntu系统的新手大多会遇到这个问题,那么我们该如何解决这个问题呢?Ubuntu在安装过程中并没有让我们设 ...
- docker 实践四:数据管理
这篇是关于 docker 的数据管理. 注:环境为 CentOS7,docker 19.03. 一般容器中管理数据主要有两种方式: 数据卷(Data Volumes):容器内数据直接映射到本地主机环境 ...
- vue设置input不可编辑切换
html: <Input name="a" v-model="formValidate.coName" placeholder="请输入姓名&q ...