北京培训记day5
高级数据结构
一、左偏树&斜堆
orz黄源河论文
合并,插入,删除根节点
打标记
struct Node { int fa,l,r,w,dep } tree[Mx];
int Merge(int k1,int k2)//返回值为根节点
{
if(k1==||k2==) return k1+k2;
if(tree[k1].val<tree[k2].val) swap(k1,k2);
tree[k1].r=Merge(tree[k1].r,k2);
tree[tree[k1].r].fa=k1;
if(tree[tree[k1].l].dep<tree[tree[k1].r].dep) swap(tree[k1].l,tree[k1].r);
if(tree[k1].r==) tree[k1].dep=;
else tree[k1].dep=tree[tree[k1].r].dep+;
return k1;
}
int del(int root)//删除根节点
{
int k1=tree[root].l,k2=tree[root].r;
l[root]=;r[root]=;fa[k1]=;fa[k2]=;
return Merge(k1,k2);
}
应用:bzoj1367【Baltic 2004】
给定一个整数序列a1,a2,…,an,求一个不下降序列b1≤b2≤…≤bn,使得数列{ai}和{bi}的各项之差的绝对值之和最小
首先给出结论:①bi是由多段相同的数组成的
②bi的每一段的数一定是ai该段的中位数
P.S.证明我也不会QAQ,自己看论文
solution:考虑若已经解决了前k个数的最优解(m个区间),记为w[1~m],
新加一个点时,先将其单独作为一个区间插入w,若w[now]<w[now-1],
则合并后两个区间,并继续判定,直到满足单调不降,最终w序列即为b序列。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define int long long
using namespace std;
const int Mx=;
int n,top,ans,a[Mx],b[Mx],L[Mx],R[Mx],stk[Mx],siz[Mx];
struct Node { int fa,l,r,val,dep; } tree[Mx];
int Merge(int k1,int k2)
{
if(k1==||k2==) return k1+k2;
if(tree[k1].val<tree[k2].val) swap(k1,k2);
tree[k1].r=Merge(tree[k1].r,k2);
tree[tree[k1].r].fa=k1;
if(tree[tree[k1].l].dep<tree[tree[k1].r].dep) swap(tree[k1].l,tree[k1].r);
if(tree[k1].r==) tree[k1].dep=;
else tree[k1].dep=tree[tree[k1].r].dep+;
return k1;
}
signed main()
{
scanf("%lld",&n);
for(int i=;i<=n;i++) scanf("%lld",&a[i]);
for(int i=;i<=n;i++)
{
tree[i].val=a[i];
stk[++top]=i;
L[top]=R[top]=i;
siz[top]=;
while(top>&&tree[stk[top]].val<=tree[stk[top-]].val)
{
R[top-]=R[top];
siz[top-]+=siz[top];
stk[top-]=Merge(stk[top-],stk[top]);
top--;
int len=R[top]-L[top]+;
if(len%==) len /= ;
else len = len / + ;
while(siz[top]>len)
{
siz[top]--;
stk[top]=Merge(tree[stk[top]].l, tree[stk[top]].r);
}
}
}
for(int i=;i<=top;i++)
for(int j=L[i];j<=R[i];j++)
b[j]=tree[stk[i]].val;
for(int i=;i<=n;i++) ans+=abs(b[i]-a[i]);
printf("%lld\n", ans);
return ;
}
二、线段树
建树,修改,查询,lazy标记
主席树,可持久化线段树
//zkw线段树
例:bzoj1146
bzoj2653
三、平衡树
旋转:splay
单旋&双旋
单旋仅在当前节点为根节点的儿子时使用
考虑当前节点与其父亲,其父亲与其祖父的关系,分为zig-zig,zig-zag
当p为根时进行zig操作
若x为p的左儿子,则p的左子树换为x的右子树,x的右儿子换为p。(对x右旋)
若x为p的右儿子,则p的右子树换为x的左子树,x的左儿子换为p。(对x左旋)
当x,p同为左(右)儿子时进行zig-zig操作
若同为左儿子,则先将p右旋,再将x右旋
若同为右儿子,则先将p左旋,再将x左旋
当x,p不同为左(右)儿子时进行zig-zag操作
若p为左孩子,x为右孩子,将x先左旋再右旋
若p为右孩子,x为左孩子,将x先右旋再左旋
#include<iostream>
using namespace std; void pushup(int rr)
{
t[rr].siz=t[t[rr].l].siz+t[t[rr].r].siz+t[rr].num;
}
void zig(int x)
{
int y=t[x].fa;
if(t[x].r)t[y].l=t[x].r,t[t[x].r].fa=y;
else t[y].l=;
if(t[t[y].fa].l==y)t[t[y].fa].l=x;
else t[t[y].fa].r=x;
t[x].fa=t[y].fa;t[x].r=y;t[y].fa=x;
pushup(y);
}
void zag(int x)
{
int y=t[x].fa;
if(t[x].l)t[y].r=t[x].l,t[t[x].l].fa=y;
else t[y].r=;
if(t[t[y].fa].l==y)t[t[y].fa].l=x;
else t[t[y].fa].r=x;
t[x].fa=t[y].fa;t[x].l=y;t[y].fa=x;
pushup(y);
}
void splay(int x,int tar)
{
while(t[x].fa!=tar)
{
int y=t[x].fa,yy=t[y].fa;
if(yy)
{
if(t[y].l==x)zig(x);
else zag(x);
}
else
{
if(t[yy].l==y)
{
if(t[y].l==x)zig(y),zig(x);
else zag(x),zig(x);
}
else
{
if(t[y].l==x)zig(x),zag(x);
else zag(y),zag(x);
}
}
}
if(!tar)root=x;
pushup(x);
}
void newnode(int &x,int fa,int key)
{
x=++tot;
t[x].pre=fa;
t[x].siz=t[x].num=;
t[x].rev=t[x].l=t[x].r=;
t[x].key=key;
}
void build(int l,int r,int &rr,int fa)
{
if(l>r)return;
int mid=(l+r)/;
newnode(rr,fa,a[mid]);
build(l,mid-,t[rr].l,rr);
build(mid+,r,t[rr].r,rr);
pushup(rr);
}
int main()
{
// 1 to n a[]
build(,n,root,);
return ;
}
treap
插入节点:先将节点插入,然后随机一个优先级,通过旋转使优先级满足堆性质。
删除:找到节点后将其旋转到叶子上并删除
查询:同二叉搜索树
//来源:@SiriusRen (我懒得写了QAQ)
//poj1442:动态插入点,查询第k大
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int cnt=,jy,a[],n,m,root=-;
struct node{
int left,right,count_left,count_right,key,priority;
}treap[];
void zig(int &x){
int y=treap[x].right;
treap[x].right=treap[y].left;
treap[x].count_right=treap[y].count_left;
treap[y].left=x;
treap[y].count_left=treap[x].count_left+treap[x].count_right+;
x=y;
}
void zag(int &x){
int y=treap[x].left;
treap[x].left=treap[y].right;
treap[x].count_left=treap[y].count_right;
treap[y].right=x;
treap[y].count_right=treap[x].count_left+treap[x].count_right+;
x=y;
}
void insert(int &x,int new_key){
if(x==-){
x=cnt++;
treap[x].left=treap[x].right=-;
treap[x].priority=rand();
treap[x].key=new_key;
treap[x].count_left=treap[x].count_right=;
}
else if(new_key<treap[x].key){
treap[x].count_left++;
insert(treap[x].left,new_key);
if(treap[x].priority>treap[treap[x].left].priority)zag(x);
}
else{
treap[x].count_right++;
insert(treap[x].right,new_key);
if(treap[x].priority>treap[treap[x].right].priority)zig(x);
}
}
int query(int &x,int k_th){
if(treap[x].count_left+==k_th)return treap[x].key;
if(treap[x].count_left+<k_th)return query(treap[x].right,k_th-treap[x].count_left-);
return query(treap[x].left,k_th);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)scanf("%d",&a[i]);
for(int i=;i<=m;i++){
scanf("%d",&jy);
while(cnt<jy)insert(root,a[cnt+]);
printf("%d\n",query(root,i));
}
}
笛卡尔树
后缀平衡树
重建:替罪羊树
在二叉搜索树的基础上拍扁重建为完全二叉树
加点:往上判断找最靠近根的不满足(siz[l]<=0.7*siz[root]&&siz[r]<=0.7*siz[root])的点,将此子树拍扁重建
删点:打标记,如果某棵子树被删掉的点>=siz/2,就将这棵子树重建
查询:与二叉搜索树相同
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int Mx=;
struct Node { int val,cnt; }tree[Mx];
int n,point,tot,l[Mx],r[Mx],fa[Mx],siz[Mx];
int num[Mx],cnt1;
void pre(int root)
{
if(root!=)
{
pre(l[root]);
if(tree[root].cnt!=) num[++cnt1]=root;
pre(r[root]);
}
}
int tmp;
void build(int lft,int rgh,int root,int jud)
{
int now=num[(lft+rgh)/];
fa[now]=root;r[now]=;l[now]=;siz[now]=;
if(jud==) l[root]=now;
else r[root]=now;
if(lft<(lft+rgh)/) build(lft,(lft+rgh)/-,now,);
if(rgh>(lft+rgh)/) build((lft+rgh)/+,rgh,now,);
siz[now]++;fa[siz[now]]+=siz[now];
}
void rebuild(int root,int jud)
{
cnt1=;
pre(root);
build(,cnt1,fa[root],jud);
}
void add(int num,int root)
{
if(num==tree[root].val)
{
tree[root].cnt++;
int temp=root;
while(fa[temp]!=)
{
temp=fa[temp];
siz[temp]++;
}
}
else if(num<tree[root].val)
{
if(l[root]==)
{
tree[++tot].val=num;tree[tot].cnt=;
l[root]=tot;fa[tot]=root;
siz[tot]=;
int temp=tot,temp1=;
while(fa[temp]!=)
{
temp=fa[temp];
siz[temp]++;
if((double)siz[l[temp]]>0.7*(double)siz[temp]||(double)siz[r[temp]]>0.7*(double)siz[temp]) temp1=temp;
}
if(temp1!=)
{
if(l[fa[temp1]]==temp1) rebuild(temp1,);
else rebuild(temp1,);
}
}
else add(num,l[root]);
}
else
{
if(r[root]==)
{
tree[++tot].val=num;tree[tot].cnt=;
r[root]=tot;fa[tot]=root;
siz[tot]=;
int temp=tot,temp1=;
while(fa[temp]!=)
{
temp=fa[temp];
siz[temp]++;
if((double)siz[l[temp]]>0.7*(double)siz[temp]||(double)siz[r[temp]]>0.7*(double)siz[temp]) temp1=temp;
}
if(temp1!=)
{
if(l[fa[temp1]]==temp1) rebuild(temp1,);
else rebuild(temp1,);
}
}
else add(num,r[root]);
}
}
int cntdel[Mx];
void cleardel(int root)
{
cntdel[root]=;
if(l[root]!=) cleardel(l[root]);
if(r[root]!=) cleardel(r[root]);
}
void del(int num)
{
int root=,tmp=;
while(tree[root].val!=num)
{
if(tree[root].val<num) root=r[root];
else root=l[root];
}
tree[root].cnt--;cntdel[root]++;siz[root]--;
while(fa[root]!=)
{
root=fa[root];cntdel[root]++;siz[root]--;
if((double) cntdel[root]>=(double) siz[root]/) tmp=root;
}
if(tmp!=)
{
if(l[fa[tmp]]==tmp) rebuild(tmp,);
else rebuild(tmp,);
cleardel(tmp);
}
}
void search_rank(int num,int root,int tmp)
{
if(num==tree[root].val) printf("%d\n",tmp+siz[l[root]]+);
else if(num<tree[root].val) search_rank(num,l[root],tmp);
else search_rank(num,r[root],tmp+siz[l[root]]+);
}
int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
int jud,a;scanf("%d%d",&jud,&a);
if(jud==) add(a,);
else if(jud==) del(a);
else search_rank(a,r[],);
}
return ;
}
分裂合并:FHQ treap
四、树套树
线段树套线段树
线段树套平衡树 (区间第k小)
树状数组套主席树
替罪羊树套主席树
五、莫队算法
例:bzoj2038【小z的袜子】
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define int long long
using namespace std;
const int Mx=;
struct Node { int l,r,num; } que[Mx];
bool cmp1 (Node a,Node b) { return a.l<b.l; }
bool cmp2 (Node a,Node b) { return a.r<b.r; }
int n,m,c[Mx],num[Mx],ans[Mx],ans1[Mx][];
inline int gcd (int a,int b) { int tmp; while(a>) tmp=b%a,b=a,a=tmp; return b; }
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=;i<=n;i++) scanf("%lld",&c[i]);
for(int i=;i<=m;i++) { scanf("%lld%lld",&que[i].l,&que[i].r);if(que[i].l>que[i].r) swap(que[i].l,que[i].r); }
for(int i=;i<=m;i++) que[i].num=i;
sort(que+,que++m,cmp1);
for(int i=;i<=m;i+=sqrt(m)) sort(que+i,que+min(m,i+(int)sqrt(m)),cmp2);
for(int i=;i<=m;i++)
{
if(i%(int)sqrt(m)==||i==)
{
memset(num,,sizeof(num));
for(int j=que[i].l;j<=que[i].r;j++) num[c[j]]++;
for(int j=;j<=n;j++) ans[i]+=num[j]*(num[j]-)/;
}
else
{
for(int j=que[i-].l,to=que[i].l;j!=to;)
{
if(j<to) ans[i]-=num[c[j]]-,num[c[j]]--,j++;
else ans[i]+=num[c[j-]],num[c[j-]]++,j--;
}
for(int j=que[i-].r,to=que[i].r;j!=to;)
{
if(j<to) ans[i]+=num[c[j+]],num[c[j+]]++,j++;
else ans[i]-=num[c[j]]-,num[c[j]]--,j--;
}
ans[i]+=ans[i-];
}
int div=gcd(ans[i],(que[i].r-que[i].l+)*(que[i].r-que[i].l)/);
if(que[i].r==que[i].l||ans[i]==) ans1[que[i].num][]=,ans1[que[i].num][]=;
else ans1[que[i].num][]=ans[i]/div,ans1[que[i].num][]=(que[i].r-que[i].l+)*(que[i].r-que[i].l)/(div*);
}
for(int i=;i<=m;i++) printf("%lld/%lld\n",ans1[i][],ans1[i][]);
return ;
}
北京培训记day5的更多相关文章
- 北京培训记day3
网络流 一.基础知识点: [容量网络] 图G(V,E)为有向网络,在V中指定一个源点和一个汇点,流量从源点出发经过有向网络流向汇点.对于每一条有向边有权值C,称作弧的容量.有向边称为弧.这样的有向网络 ...
- 北京培训记day2
后缀三姐妹 P.S.后缀大家族关系:后缀自动机fail指针=后缀树,后缀树前序遍历=后缀数组 一.后缀数组:orz罗穗骞集训队论文 给每个后缀按字典序排序 rank[]表示从i开始的后缀排名多少 sa ...
- 北京培训记day1
数学什么的....简直是丧心病狂啊好不好 引入:Q1:前n个数中最多能取几个,使得没有一个数是另一个的倍数 答案:(n/2)上取整 p.s.取后n/2个就好了 Q2:在Q1条件下,和最小为多少 答 ...
- 北京培训记day4
智商题QAQ-- T1:求>=n的最小素数,n<=10^18 暴力枚举n-n+100,miller-rabin筛法 T2:给定一个01矩阵,每次选择一个1并将(x,y)到(1,1)颜色反转 ...
- 我的屌丝giser成长记-研二篇
之前有提到过的,本来按照计划中,研一结束就该去深圳中科院研究所实习的,之前跟里面师兄说好了的,奈何导师又接到一个新的科研研究项目,跟学院的几个其他老师一起合作的,主要是关于土地流转系统,而且是一个挺大 ...
- Python基础篇-day5
本节目录: 1.生成器 1.1 列表推导式方法 1.2 函数法--适用复杂的推导方法2.迭代器3.装饰器 3.1 单一验证方式(调用不传参数) 3.2 单一验证方式(调用传参数) 3.3 多种验证方式 ...
- .Net面试经验,从北京到杭州
首先简单说下,本人小本,目前大四软件工程专业,大三阴差阳错地选了.Net方向,也是从大三开始接触.Net.自认为在学生中.net基础还可以,嘿嘿,吹一下. 大四第一学期学校安排去北京培训,培训了两个月 ...
- Java游戏服务器成长之路——感悟篇
又是一个美好的周末啊,现在一到周末,早上就起得晚,下午困了又会睡一两个小时,上班的时候,早上起来喝一杯咖啡,然后就能高效的工作一整天,然而到了周末人就懒散了,哈哈. 最近刚跳槽,到新公司已经干了有两周 ...
- python中常用的模块的总结
1. 模块和包 a.定义: 模块用来从逻辑上组织python代码(变量,函数,类,逻辑:实现一个功能),本质就是.py结尾的python文件.(例如:文件名:test.py,对应的模块名:test) ...
随机推荐
- SSH远程登录原理与运用
SSH是每一台Linux电脑的标准配置. 随着Linux设备从电脑逐渐扩展到手机.外设和家用电器,SSH的使用范围也越来越广.不仅程序员离不开它,很多普通用户也每天使用. SSH具备多种功能,可以用于 ...
- 十种MYSQL显错注入原理讲解(二)
上一篇讲过,三种MYSQL显错注入原理.下面我继续讲解. 1.geometrycollection() and geometrycollection((select * from(select * f ...
- Android 5.X新特性之为RecyclerView添加下拉刷新和上拉加载及SwipeRefreshLayout实现原理
RecyclerView已经写过两篇文章了,分别是Android 5.X新特性之RecyclerView基本解析及无限复用 和 Android 5.X新特性之为RecyclerView添加Header ...
- iOS10 适配问题-Xcode8
前段时间升级了Xcode8,整体来说对OC的影响不大,但是还是跳一个坑,消耗了不少时间.这里总结下遇到的适配问题. 1.权限问题 Xcode8 访问相机.相册等需要权限的地方崩溃 解决办法: 在使用私 ...
- DX12龙书第6章习题
1. { { , DXGI_FORMAT_R32G32B32_FLOAT, , , D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, }, { , DXGI_FO ...
- Learning the standard of C++11
It's a very useful website of en.cppreference.com. It lists a huge number of information about the l ...
- SE Springer小组《Spring音乐播放器》软件需求说明3
3 需求规定 3.1对功能的规定 基本功能与相关的输入输出如下表所示.歌曲播放.停止.暂停等功能调用MCI库,数据在MCI库下如何运作与用户的直观感受无关,就不具体列出. 输入 处理 输出 用户登录信 ...
- [Erlang 0112] Elixir Protocols
Why Elixir 为什么要学习Elixir?答案很简单,为了更好的学习Erlang.这么无厘头的理由? Erlang语法设计几乎没有考虑过取悦开发者,所以学习之初的门槛略高.对于已经克服了最初 ...
- Java 加解密技术系列文章
Java 加解密技术系列之 总结 Java 加解密技术系列之 DH Java 加解密技术系列之 RSA Java 加解密技术系列之 PBE Java 加解密技术系列之 AES Java 加解密技术系列 ...
- LeetCode题解-----Wildcard Matching
题目描述: '?' Matches any single character. '*' Matches any sequence of characters (including the empty ...