ImmortalWatcher
2020-04-07 08:18:37
由于作者是一个初二蒟蒻,有一些地方可能存在问题,请多指教。喷轻点。
我与
当然还有一篇已经过了但被管理员咕掉的日报浅谈几种插值方法,由于
upd 2020.9.20: 好像已经解决了……
在很久很久以前,有一道大难题诞生了……
HDU 1506 最大子矩形
题目大意:
阴影部分就是图中的最大子矩阵。
大佬
的确,这道题十分的简单,一个
然后这道题就被解决了。
读者:“作者你是不是找死!”
作者:“疼疼疼,别扔鸡蛋了,鸡蛋很贵的!”
这道题真的解决了吗?对于我这样的
难道就没有简单一点的算法?
读者:“作者你是不是又找死!”
作者:“疼疼疼,别扔鸡蛋了,我正经一点还不行吗!”
经过几万年的时间,一个算法横空出世了!
它是一个与平衡树和堆密切相关的数据结构。
它在操作少和数据随机的情况下能很好的解决一些区间最值问题。
它就是——
那为了不让读者再次向我扔鸡蛋,我们就开始打开笛卡尔树的大门吧。
笛卡尔树是一种特定的二叉树数据结构,可由数列构造,在范围最值查询、
范围top k查询(range top k queries)等问题上有广泛应用。它具有堆的有序性,中序遍历可以输出原数列。
笛卡尔树结构由Vuillmin(1980)在解决范围搜索的几何数据结构问题时提出。
从数列中构造一棵笛卡尔树可以线性时间完成,需要采用基于栈的算法来找到在该数列中的所有最近小数。
——摘自百度百科
读了上述文字,相信你对笛卡尔树有了一定的了解。也知道了它的用处。那么一棵笛卡尔树怎么构造呢?
我们先来看一棵笛卡尔树。
观察这一棵笛卡尔树,你发现了什么?
我们定义,数组的元素值当做键值
对于一个无重复元素的笛卡尔树(如上图)有以下性质。
for (int i=1;i<=n;i++)
{
int k=top;//top表示操作前栈顶,k表示操作中栈顶
while (k>0&&h[stk[k]]>h[i]) k--;
if (k) rs[stk[k]]=i; //rs代表笛卡尔树每个节点的右儿子
//栈未空,栈顶右儿子等于当前元素
if (k<top) ls[i]=stk[k+1]; //ls代表笛卡尔树每个节点的左儿子
//退了栈,当前元素左儿子等于前一个退栈元素
stk[++k]=i;//当前元素入栈
top=k;
}
时间复杂度:
然后你们就可以
祝你们好运!
HDU 1506 最大子矩形
题目大意:
我们将下标当做键值
这道题还是比较简单的,代码部分就交给读者们啦。
P3246 [HNOI2016]序列
这道题的题目大意就是说,给你
虽然这道题可以离线莫队做,但是我们这里只讲笛卡尔树的做法。
首先
设
考虑从
设
那么
就有转移
我们发现
得到
然后对它做一个前缀和,设为
我们回到原来的问题。
设
那么如果子区间跨过
所以这一段的贡献就是
剩下考虑
对于
有人就会问了,那我们一开始建立笛卡尔树干什么呢?
还记得
#include<cstdio>
using namespace std;
int a[100001],ls[100001],rs[100001],s[100001],top,n,rt,last[100001];
int next[100001],m,l,r;
long long fr[100001],fl[100001],gr[100001],gl[100001];
int query(int l,int r)
{
for (int i=rt;;i=i>r?ls[i]:rs[i])
if (l<=i&&i<=r) return i;
}
int main()
{
scanf("%d%d",&n,&m);a[0]=a[n+1]=0x3f3f3f3f;
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
{
int k=top;
while (k>0&&a[s[k]]>a[i]) k--;
if (k) rs[s[k]]=i;
if (k<top) ls[i]=s[k+1];
s[++k]=i;
top=k;
}
rt=s[1],top=0;
for (int i=1;i<=n;i++)
{
while(top&&a[s[top]]>a[i]) next[s[top--]]=i;
last[i]=s[top],s[++top]=i;
}
while(top) last[s[top]]=s[top-1],next[s[top--]]=n+1;
for (int i=1;i<=n;i++) fr[i]=fr[last[i]]+1ll*a[i]*(i-last[i]),gr[i]=gr[i-1]+fr[i];
for (int i=n;i>=1;i--) fl[i]=fl[next[i]]+1ll*a[i]*(next[i]-i),gl[i]=gl[i+1]+fl[i];
for (int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
int p=query(l,r);
printf("%lld\n",1ll*(p-l+1)*(r-p+1)*a[p]+gr[r]-gr[p]-fr[p]*(r-p)+gl[l]-gl[p]-1ll*fl[p]*(p-l));
}
return 0;
}
P4755 Beautiful Pair
建一棵笛卡尔树。
考虑在笛卡尔树上分治。
假设我们当前分治到区间
根据分治套路,我们只需要计算跨过
我们来看看跨过
首先启发式分裂一下,如果
我们这里只讲枚举左边去算右边的贡献的情况,另一种同理。
枚举点对的左端点,由于
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[100001],b[100001],top,len,rt,root[100001],ls[100001],tot;
int rs[100001],stk[100001];
long long ans;
struct node{int l,r,sum;} tree[2000001];
int get(int x) {return lower_bound(b+1,b+len+1,x)-b;}
void change(int &rt,int k,int l,int r,int x)
{
if(l>x||r<x) return;
rt=++tot;
tree[rt].sum=tree[k].sum+1;
tree[rt].l=tree[k].l;
tree[rt].r=tree[k].r;
if(l==r) return;
int mid=(l+r)>>1;
change(tree[rt].l,tree[k].l,l,mid,x);
change(tree[rt].r,tree[k].r,mid+1,r,x);
}
int query(int rt,int k,int l,int r,int x)
{
if(l>x) return 0;
if(r<=x) return tree[rt].sum-tree[k].sum;
int mid=l+r>>1,ret=0;
ret+=query(tree[rt].l,tree[k].l,l,mid,x);
ret+=query(tree[rt].r,tree[k].r,mid+1,r,x);
return ret;
}
void solve(int l,int r,int pos)
{
if(l>r)return;
if(l==r){ans+=(b[a[l]]==1);return;}
if(pos-l+1<=r-pos)
for(int i=l;i<=pos;i++)
{
int k=upper_bound(b+1,b+len+1,b[a[pos]]/b[a[i]])-b-1;
ans+=query(root[r],root[pos-1],1,len,k);
}
else
for(int i=pos;i<=r;i++)
{
int k=upper_bound(b+1,b+len+1,b[a[pos]]/b[a[i]])-b-1;
ans+=query(root[pos],root[l-1],1,len,k);
}
solve(l,pos-1,ls[pos]);solve(pos+1,r,rs[pos]);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
len=unique(b+1,b+n+1)-b-1;
for (int i=1;i<=n;i++)
{
int k=top;
while (k>0&&a[stk[k]]<a[i]) k--;
if (k) rs[stk[k]]=i;
if (k<top) ls[i]=stk[k+1];
stk[++k]=i;
top=k;
}
rt=stk[1];
for (int i=1;i<=n;i++) a[i]=get(a[i]);
for (int i=1;i<=n;i++) change(root[i],root[i-1],1,len,a[i]);
solve(1,n,rt);
printf("%lld",ans);
return 0;
}
P2611 [ZJOI2012]小蓝的好友
P5044 [IOI2018] meetings 会议
其他平衡树的题大佬们也可以去做做(感谢@万万没想到 的提醒)
其实标准
感谢一些学术网站以及一些大佬的博客,在这里可能引用了他们的图和题,这里声明一下.(感谢@BoringHacker 的提醒)
OI wiki
大佬的博客
大佬的博客2
upd 2020.08.07:做一个小小的统计