题目大意:给你一个长度为$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序的更多相关文章

  1. 树的dfs序 && 系统栈 && c++ rope

    利用树的dfs序解决问题: 就是dfs的时候记录每个节点的进入时间和离开时间,这样一个完整的区间就是一颗完整的树,就转化成了区间维护的问题. 比如hdu3887 本质上是一个求子树和的问题 #incl ...

  2. [2]树的DFS序

    定义: 树的DFS序就是在对树进行DFS的时候,对树的节点进行重新编号:DFS序有一个很强的性质: 一颗子树的所有节点在DFS序内是连续的一段, 利用这个性质我们可以解决很多问题. 代码: void ...

  3. CF877E Danil and a Part-time Job 线段树维护dfs序

    \(\color{#0066ff}{题目描述}\) 有一棵 n 个点的树,根结点为 1 号点,每个点的权值都是 1 或 0 共有 m 次操作,操作分为两种 get 询问一个点 x 的子树里有多少个 1 ...

  4. BZOJ_3729_Gty的游戏_博弈论+splay+dfs序

    BZOJ_3729_Gty的游戏_博弈论+splay+dfs序 Description 某一天gty在与他的妹子玩游戏. 妹子提出一个游戏,给定一棵有根树,每个节点有一些石子,每次可以将不多于L的石子 ...

  5. HDU4117 GRE WORDS(AC自动机+线段树维护fail树的dfs序)

    Recently George is preparing for the Graduate Record Examinations (GRE for short). Obviously the mos ...

  6. 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 ...

  7. 【BZOJ3991】寻宝游戏(虚树,DFS序,splay)

    题意:求在树中从任意点开始,经过若干个关键点回到原点的最小距离 要求支持在线将某个点设置(取消)为关键点,以及询问答案 n,m<=100000 len[i]<=10^9 思路:显然是一个虚 ...

  8. BZOJ2780: [Spoj]8093 Sevenk Love Oimaster(广义后缀自动机,Parent树,Dfs序)

    Description Oimaster and sevenk love each other. But recently,sevenk heard that a girl named ChuYuXu ...

  9. BZOJ3786: 星系探索 Splay+DFS序

    题目大意:给你一个树,支持三种操作,子树加,点到根的路径和,改变某一个点的父亲. 分析: 看起来像一个大LCT,但是很显然,LCT做子树加我不太会啊... 那么,考虑更换一个点的父亲这个操作很有意思, ...

随机推荐

  1. 一个unsigned int 数的二进制表示中有多少个1

    这是一道面试题可以用以下的一些方案.第一种是很容易想到的采用循环的方式并且与1进行位与运算,具体代码如下.  1unsigned int GetBitNumOfOne_ByLoop1(unsigned ...

  2. windows添加“以管理员身份运行”

    方法: 新建一个txt文件,命名为"admin.txt",记得打开"显示后缀名",要求看到.txt并可修改之. 将下列代码粘贴进去. Windows Regis ...

  3. (模板)hdoj1251(字典树模板题)

    题目链接:https://vjudge.net/problem/HDU-1251 题意:给定一系列字符串之后,再给定一系列前缀,对每个前缀查询以该字符串为前缀的字符串个数. 思路: 今天开始学字典树, ...

  4. Gulp-构建工具 相关内容整理

    Gulp- 简介 Automate and enhance your workflow | 用自动化构建工具增强你的工作流程 Gulp 是什么? gulp是前端开发过程中一种基于流的代码构建工具,是自 ...

  5. Object 方法的 hashCode,equals方法源码

    文章目录 hashCode方法注释 equals 方法注释 equals 方法 hashCode方法注释 Object 的 hashCode 方法,是本地方法: Returns a hash code ...

  6. WUSTOJ 1341: Lake and Island(Java)

    题目链接:1341: Lake and Island Description 北园孩子的专属福利来啦~学校从北区宿舍到湖心岛修建了一条通道让北园的同学们可以上去一(kuang)同(xiu)玩(en)耍 ...

  7. 多进程实现并发服务器(TCP)

    前提:基于Linux系统的学习 /*多进程实现并发服务器 父进程任务 1.从未决连接队列中取出请求,建立和客户端的连接,cfd 2.创建子进程 3.close(cfd) 4.负责子进程的收尸工作 子进 ...

  8. Vue、SPA实现登陆

    axios/qs/vue-axios安装及使用步骤 首先我们要下载三个依赖包,方便后面的开发使用需要: npm install axios -S   axios是vue2提倡使用的轻量版的ajax.它 ...

  9. Python 2.7.x 和 3.x 版本的重要区别小结

    许多Python初学者都会问:我应该学习哪个版本的Python.对于这个问题,我的回答通常是"先选择一个最适合你的Python教程,教程中使用哪个版本的Python,你就用那个版本.等学得差 ...

  10. MySQL中You can't specify target table '表名'('sn_app_label') for update in FROM clause错误解决办法

    在有些时候有级联关系的数据放在了同一张表中,在写sql语句的时候可能会遇到这样的场景:我要插入两条数据,第一条是父节点,第二条是子节点,关联关系是父节点的自增长id:在写这样的sql语句时有可能就会出 ...