【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做子树加我不太会啊... 那么,考虑更换一个点的父亲这个操作很有意思, ...
随机推荐
- linux中硬盘分区、格式化、挂载
已经接触了小半年的linux,基本命令用的还行,就是涉及到深入操作,就显得不够看了,比如linux中的硬盘操作,于是整理了这篇博客. 1. 主分区,扩展分区,逻辑分区的联系和区别 一个硬盘可以有1 ...
- 使用plotrix做韦恩图
color <- c("#E41A1C","#377EB8","#FDB462") color_transparent <- a ...
- 《ucore lab1》实验报告
资源 ucore在线实验指导书 我的ucore实验代码 练习1:理解通过make生成执行文件的过程 详见<ucore lab1 exercise1>实验报告 练习2:使用qemu执行并调试 ...
- Net Core 3 Mvc AliPay Demo
AliPay - PC 钻研了几天的Webpack, 这几天回归了我的本行.Net, 跟随大佬的脚步, 开始做上了支付宝的支付. 创建项目 首先创建一基于.Net Core3.0的MVC项目, 然后引 ...
- Nginx 系列教程
Nginx(一):Nginx介绍 Nginx(二):编译安装Nginx及参数说明 Nginx(三):nginx.conf配置文件说明 [1] 配置参数说明 Nginx(三):nginx.conf配置文 ...
- [VS] - 手工打开 WCF 客户端调试工具
操作步骤 1. 在开始菜单中找到 Visual Studio 命令行工具 2. 输入命令 wcftestclient 即可打开 WCF 客户端测试工具 参考资料http://www.cnblogs.c ...
- 综合应用,jieba,去标点,分词保存,统计,删词,输出
import jieba fp1=r'D:/python/a.txt' outph=r'D:/python/out.txt' f=open(fp1,'r',encoding='utf-8') txt= ...
- Mysql union和union all用法
1: 什么时候用union和union all ? 我们经常会碰到这样的应用,两个表的数据按照一定的查询条件查询出来以后,需要将结果合并到一起显示出来,这个时候 就需要用到union和union ...
- Linux入职基础-1.2_U盘安装RedHat5具体步骤
从U盘安装RedHat5具体步骤 从U盘安装RedHat Linux的具体步骤: 准备工作: RHEL_5.6_i386_DVD.ISO文件 具体步骤: 1.解压并用ultraiso软件打开rhel- ...
- C# UTF-8文件带BOM和不带BOM文件的转换
读取INI文件使用的是GetPrivateProfileString方法,自己读写ini文件没有问题. 调用C++的API对同一个ini文件进行处理后,发现首个Section的值读不出来:发现是API ...