ACPC
2024-10-30 11:47:28
前置知识:
析合树 板子题。
本篇的图片来自 codeforces。
给定长度
n 的排列,求区间个数,使得区间内所有数排序后相邻数字相差均为1 。
抛两个定义:
可以很容易地发现,任意两个本原连续段
如图,将每个本原连续段作为节点建造一棵这样的树,本原连续段节点按照区间的形式给出,此为析合树。
再抛四个定义:
根据定义,合、析点具有以下性质:
接下来是析合树的构造。
考虑使用一个栈,假设
但是找到这个
考虑记录一个
实际上,一段
记录一个
现在假设要从枚举的右端点
可以考虑单调栈维护一个
对于一段的加操作以及求
对于板子题。显然可以把矩阵转换序列,对于每个有怪物的节点
显然,对于每个合点,儿子序列构成的所有连续子集(包含儿子个数需要
综上所述,构造完析合树之后直接计数即可。
这里感谢 @__er 和 @tbdsh 做出了一些帮助。
#include <bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
int res=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') {res=res*10+(c-'0');c=getchar();}
return res*f;
}
const int N=3e5+5;
int n,a[N],L[N],Q[N<<1],LOG[N],T[N<<1];
vector<int>son[N<<1],mode;
struct Stack{//手写栈
int tip,Stk[N<<1];
void push(int x) {Stk[++tip]=x;}
int top() {return Stk[tip];}
void pop() {tip--;}
int size() {return tip;}
bool empty() {return !tip;}
int get(int x) {return Stk[x];}
void clear() {tip=0;}
}s1,s2,s;
struct dot{
int l,r,tmp;
}d[N<<1];
struct ST{//ST 表,区间求最值
#define k LOG[r-l+1]
int fax[N][25],fin[N][25];
void build(){//O(n log n)
for (int i=1;i<=n;++i) fax[i][0]=fin[i][0]=a[i];
for (int j=1;j<25;++j)
for (int i=1;i+(1<<j)-1<=n;++i){
fax[i][j]=max(fax[i][j-1],fax[i+(1<<(j-1))][j-1]);
fin[i][j]=min(fin[i][j-1],fin[i+(1<<(j-1))][j-1]);
}
for (int i=2;i<=N-5;i++) LOG[i]=LOG[i>>1]+1;
}
bool query(int l,int r){//O(1)
return (max(fax[l][k],fax[r-(1<<k)+1][k])-min(fin[l][k],fin[r-(1<<k)+1][k])==r-l);
}
#undef k
}st;
struct sgm{//线段树
#define lc (x<<1)
#define rc (x<<1|1)
int tag[N<<2],mn[N<<2],ans[N<<2];
void push_up(int x){//O(1)
mn[x]=min(mn[lc],mn[rc]);
ans[x]=ans[lc]+ans[rc];
}
void push_down(int x){//O(1)
if (!tag[x]) return;
tag[lc]+=tag[x],tag[rc]+=tag[x];
mn[lc]+=tag[x],mn[rc]+=tag[x];
tag[x]=0;
}
void update(int x,int l,int r,int ql,int qr,int d){//O(log n)
if (ql>qr) return;
if (ql<=l&&qr>=r){
tag[x]+=d,mn[x]+=d,ans[x]+=d;
return;
}
push_down(x);
int mid=(l+r)>>1;
if (ql<=mid) update(lc,l,mid,ql,qr,d);
if (qr>mid) update(rc,mid+1,r,ql,qr,d);
push_up(x);
}
int query(int x,int l,int r){//O(log n)
if (l==r) return l;
push_down(x);
int mid=(l+r)>>1;
if (!mn[lc]) return query(lc,l,mid);
else return query(rc,mid+1,r);
}
#undef lc
#undef rc
}book;
void Init(){//O(n log n)
//这里是初始化 L 数组用的函数
for (int i=1;i<=n;++i){
while (!s1.empty()&&a[i]<=a[s1.top()]){
int x=s1.top();
s1.pop();
book.update(1,1,n,s1.top()+1,x,a[x]);
}
while (!s2.empty()&&a[i]>=a[s2.top()]){
int x=s2.top();
s2.pop();
book.update(1,1,n,s2.top()+1,x,-a[x]);
}
book.update(1,1,n,s1.top()+1,i,-a[i]);
book.update(1,1,n,s2.top()+1,i,a[i]);
s1.push(i),s2.push(i);
L[i]=book.query(1,1,n);
book.update(1,1,n,1,i,-1);
}
// for (int i=1;i<=n;i++) cout<<L[i]<<' ';cout<<'\n';
}
int cnt;
void Build_Tree(){//O(n)
//建立析合树
for (int i=1;i<=n;i++){
d[++cnt]={i,i,0};
int now=cnt;
while (!s.empty()&&d[s.top()].l>=L[i]){
int x=s.top();
if (d[x].tmp&&st.query(T[x],i)){
d[x].r=i,T[x]=d[now].l,son[x].push_back(now);
now=x,s.pop();
}
else if (st.query(d[x].l,i)){
d[++cnt]={d[x].l,d[now].r,1};
son[cnt].push_back(x),son[cnt].push_back(now);
T[cnt]=d[now].l,now=cnt,s.pop();
}
else{
do{
mode.push_back(s.top());
s.pop();
}
while (!s.empty()&&!st.query(d[s.top()].l,i));
d[++cnt]={d[s.top()].l,i,0};
son[cnt].push_back(s.top());
for (auto v:mode) son[cnt].push_back(v);
son[cnt].push_back(now),now=cnt;
s.pop(),mode.clear();
}
}
s.push(now);
// cout<<d[s.top()].l<<' '<<d[s.top()].r<<'\n';
}
}
signed main(){
n=read();
for (int i=1;i<=n;++i){
int __=read();
a[__]=read();
}
Init(),st.build();
Build_Tree();
int ans=0;
for (int i=1;i<=cnt;++i){//O(n)
// cout<<d[i].l<<' '<<d[i].r<<'\n';
if (d[i].tmp){
int x=son[i].size();
ans+=x*(x-1)>>1;//计算答案,建树后过程并不复杂
}
else ans++;
}
cout<<ans;
return 0;
}
例题:P4747,析合树的基础运用。
我们考虑对排列建出析合树。
对于给定的区间
那么需要考虑通过本原连续段
时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
int res=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') {res=res*10+(c-'0');c=getchar();}
return res*f;
}
const int N=1e5+5;
int n,a[N],L[N],Q[N<<1],LOG[N],T[N<<1],dz[N];
vector<int>son[N<<1],mode;
struct Stack{
int tip,Stk[N];
void push(int x) {Stk[++tip]=x;}
int top() {return Stk[tip];}
void pop() {tip--;}
int size() {return tip;}
bool empty() {return !tip;}
int get(int x) {return Stk[x];}
void clear() {tip=0;}
}s1,s2,s;
struct dot{
int l,r,tmp;
}d[N<<1];
struct ST{
#define k LOG[r-l+1]
int fax[N][17],fin[N][17];
void build(){//O(n log n)
for (int i=1;i<=n;i++) fax[i][0]=fin[i][0]=a[i];
for (int j=1;j<17;j++)
for (int i=1;i+(1<<j)-1<=n;i++){
fax[i][j]=max(fax[i][j-1],fax[i+(1<<(j-1))][j-1]);
fin[i][j]=min(fin[i][j-1],fin[i+(1<<(j-1))][j-1]);
}
for (int i=2;i<=N-5;i++) LOG[i]=LOG[i>>1]+1;
}
bool query(int l,int r){//O(1)
return (max(fax[l][k],fax[r-(1<<k)+1][k])-min(fin[l][k],fin[r-(1<<k)+1][k])==r-l);
}
#undef k
}st;
struct sgm{
#define lc (x<<1)
#define rc (x<<1|1)
int tag[N<<2],mn[N<<2],ans[N<<2];
void push_up(int x){//O(1)
mn[x]=min(mn[lc],mn[rc]);
ans[x]=ans[lc]+ans[rc];
}
void push_down(int x){//O(1)
if (!tag[x]) return;
tag[lc]+=tag[x],tag[rc]+=tag[x];
mn[lc]+=tag[x],mn[rc]+=tag[x];
tag[x]=0;
}
void update(int x,int l,int r,int ql,int qr,int d){//O(log n)
if (ql>qr) return;
if (ql<=l&&qr>=r){
tag[x]+=d,mn[x]+=d,ans[x]+=d;
return;
}
push_down(x);
int mid=(l+r)>>1;
if (ql<=mid) update(lc,l,mid,ql,qr,d);
if (qr>mid) update(rc,mid+1,r,ql,qr,d);
push_up(x);
}
int query(int x,int l,int r){//O(log n)
if (l==r) return l;
push_down(x);
int mid=(l+r)>>1;
if (!mn[lc]) return query(lc,l,mid);
else return query(rc,mid+1,r);
}
#undef lc
#undef rc
}book;
void Init(){//O(n log n)
for (int i=1;i<=n;i++){
while (!s1.empty()&&a[i]<=a[s1.top()]){
int x=s1.top();
s1.pop();
book.update(1,1,n,s1.top()+1,x,a[x]);
}
while (!s2.empty()&&a[i]>=a[s2.top()]){
int x=s2.top();
s2.pop();
book.update(1,1,n,s2.top()+1,x,-a[x]);
}
book.update(1,1,n,s1.top()+1,i,-a[i]);
book.update(1,1,n,s2.top()+1,i,a[i]);
s1.push(i),s2.push(i);
L[i]=book.query(1,1,n);
book.update(1,1,n,1,i,-1);
}
// for (int i=1;i<=n;i++) cout<<L[i]<<' ';cout<<'\n';
}
int cnt;
void Build_Tree(){//O(n)
for (int i=1;i<=n;i++){
d[++cnt]={i,i,0};
int now=cnt;
while (!s.empty()&&d[s.top()].l>=L[i]){
int x=s.top();
if (d[x].tmp&&st.query(T[x],i)){
d[x].r=i,T[x]=d[now].l,son[x].push_back(now);
now=x,s.pop();
}
else if (st.query(d[x].l,i)){
d[++cnt]={d[x].l,d[now].r,1};
son[cnt].push_back(x),son[cnt].push_back(now);
T[cnt]=d[now].l,now=cnt,s.pop();
}
else{
do{
mode.push_back(s.top());
s.pop();
}
while (!s.empty()&&!st.query(d[s.top()].l,i));
d[++cnt]={d[s.top()].l,i,0};
son[cnt].push_back(s.top());
for (auto v:mode) son[cnt].push_back(v);
son[cnt].push_back(now),now=cnt;
s.pop(),mode.clear();
}
}
s.push(now);
// cout<<d[s.top()].l<<' '<<d[s.top()].r<<'\n';
}
}
vector<int>e[N<<1];
int dep[N<<1],siz[N<<1],fa[N<<1],Bson[N<<1];
int dfs(int u,int father){
dep[u]=dep[father]+1;
fa[u]=father;
siz[u]=1;
int maxn=-1;
for (auto v:e[u]){
if (v==father) continue;
int sizes=dfs(v,u);
siz[u]+=sizes;
if (sizes>maxn) maxn=sizes,Bson[u]=v;
}
return siz[u];
}
int dfn[N<<1],top[N<<1],tip=1;
void dfs2(int u,int father,int op){
if (op==1) top[u]=top[father];
dfn[u]=tip++;
if (Bson[u]!=0) dfs2(Bson[u],u,1);
for (auto v:e[u]){
if (v==father||v==Bson[u]) continue;
dfs2(v,u,0);
}
}
void init(int root){
dfs(root,0);
for (int i=1;i<=n;i++) top[i]=i;
dfs2(root,0,0);
}
int lca(int x,int y){
if (top[x]==top[y]) return dep[x]>dep[y]?y:x;
if (dep[top[x]]<dep[top[y]]) swap(x,y);
return lca(fa[top[x]],y);
}
signed main(){
n=read();
for (int i=1;i<=n;i++){
int __=i;
a[__]=read();
}
Init(),st.build();
Build_Tree();
int rt=0;
for (int i=1;i<=cnt;i++){
if (d[i].l==1&&d[i].r==n) rt=i;
if (d[i].l==d[i].r) dz[d[i].l]=i;
for (auto j:son[i]){
e[i].push_back(j);
e[j].push_back(i);
// cout<<d[i].l<<','<<d[i].r<<' '<<d[j].l<<','<<d[j].r<<'\n';
}
}
n=cnt,init(rt);
int Qr=read();
while (Qr--){//与模板不同的是,这里的求答案较为复杂,但事实上不难
int dl=read(),dr=read(),_lca=lca(dz[dl],dz[dr]);
if (!d[_lca].tmp){
cout<<d[_lca].l<<' '<<d[_lca].r<<'\n';
continue;
}
int _l=0,_r=son[_lca].size()-1,t1,t2;
while (_l<_r){
int mid=(_l+_r+1)>>1,x=son[_lca][mid];
if (d[x].l<=dl) _l=mid;
else _r=mid-1;
}
t1=son[_lca][_l],_l=0,_r=son[_lca].size()-1;
while (_l<_r){
int mid=(_l+_r)>>1,x=son[_lca][mid];
if (d[x].r>=dr) _r=mid;
else _l=mid+1;
}
t2=son[_lca][_l];
cout<<d[t1].l<<' '<<d[t2].r<<'\n';
}
return 0;
}
当然,析合树考的可能比较灵活,比如 CF1089I,一道运用其思想的好题。
对排列构建出析合树之后,题意可以被转化为:求使得析合树的节点个数为
记录一个
根据题意,析合树节点个数
对于根为合点情况,根据合点性质,存在一个排列的前缀使得其为值域