【20200206】【lyk】CF1290E 笛卡尔树(吉司机线段树套树状数组)题解

探讨了求解1-n排列下,最小x个数构成的子序列的笛卡尔树子树大小之和的问题。通过吉司机线段树与树状数组结合,实现了高效算法,总复杂度为单log。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本来是昨天的题,昨天补习了一下前置技能吉司机线段树

题意

给你一个1-n的排列,求最小的x个数构成的子序列的笛卡尔树的子树大小之和,x遍历1-n。

思路

考虑再原序列中插入下一个数带来的变化。观察笛卡尔树性质,每个结点对应一个区间,该节点子树大小等于区间长度。设maxr(l)表示所有以l为左端点的结点中r的最大值。对称的定义minl( r )。

关键:任意一个非根节点,minl( r ) = l 和maxr( l ) = r 有且只有一个成立,所以简单容斥一下发现答案 = sum(p(maxr(l))) + sum(p( r )) - sum(p(minl( r ))) - sum(p(l)) - n(n为总结点数,p为区间内节点数前缀和)

然后就是用吉司机线段树套树状数组维护这个和(两个树状数组,一个维护前缀和使用频率(即每个p(x)用了多少个),一个维护前缀和,吉司机线段树维护maxr(l),主要是区间和某个数取min和单点修改,总复杂度单log,每次线段树上修改时对频率树状数组做单点修改,总复杂度log^2)。乱七八糟的细节很多,但是样例很良心~~~~

值得一提的是先计算所有maxr(l) = r的结点,在计算剩下的结点时只需把整个序列翻转再跑一遍原来的程序即可。

#include <bits/stdc++.h>

using namespace std;
const int maxn = 150505;
struct tree_array {
	int c[maxn],tot;
	inline void Add(int x,int val) {
		if (!x)
			return ;
	    for(int i = x;i <= tot;i += (i&-i)) c[i] += val;
	}
	int getsum(int st,int ed) {
	    int ret = 0;
	    for (int i = ed;i;i -= (i&-i)) ret += c[i];
	    if (st >= 0)
	    	for (int i = st;i;i -= (i&-i)) ret -= c[i];
	    return ret;
	}
	inline void clr() {
		tot = 0;
		memset(c,0,sizeof(c));
	}
}A,B;
int ch[maxn],df[maxn],n,a[maxn],p[maxn];
struct Node {
	Node *l,*r;
	int st,ed,mx,se,cnt,lazy;
}pool[303030],*root;
int z = 0,tot = 0;
inline void down(Node *now) {
	now->l->mx = min(now->mx,now->l->mx);
	now->r->mx = min(now->mx,now->r->mx);
}
inline void update(Node *now) {
	if (now->l->mx == now->r->mx) {
		now->cnt = now->l->cnt+now->r->cnt;
		now->se = max(now->l->se,now->r->se); 
		now->mx = now->l->mx;
	}
	else if (now->l->mx > now->r->mx) {
		now->mx = now->l->mx;
		now->cnt = now->l->cnt;
		now->se = max(now->r->mx,now->l->se);
	}
	else {
		now->mx = now->r->mx;
		now->cnt = now->r->cnt;
		now->se = max(now->l->mx,now->r->se);
	}
}
inline void Build(Node *&now,int st,int ed) {
	now = &pool[++z];
	now->st = st;
	now->ed = ed;
	if (st == ed) {
		now->se = -1e9;
		now->mx = 0;
		now->cnt = 1;
		return ;
	}
	Build(now->l,st,(st+ed)/2);
	Build(now->r,(st+ed)/2+1,ed);
	update(now);
}
inline void clr(Node *now) {
	if (now->st == now->ed) {
		now->se = -1e9;
		now->mx = 0;
		now->cnt = 1;
	}
	else {
		clr(now->l);
		clr(now->r);
		update(now);
	}
}
inline void ins(Node *now,int pos,int val) {
	if (now->st > pos || now->ed < pos)
		return ;
	if (now->st == now->ed) {
		ch[++tot] = now->mx;
		df[tot] = -1;
		now->mx = val;
		ch[++tot] = now->mx;
		df[tot] = 1; 
		return ;
	}
	down(now);
	ins(now->l,pos,val);
	ins(now->r,pos,val);
	update(now);
}
inline void IntervalMin(Node *now,int st,int ed,int val) {
//	cout << now->st << " " << now->ed << " " <<now->mx << " "<< now->cnt<<" "<<st << " " << ed <<" " <<  val << endl;
	if (now->st > ed || now->ed < st || now->mx <= val)
		return ;
	if (now->st >= st && now->ed <= ed && now->se < val) {
		ch[++tot] = now->mx;
		df[tot] = -now->cnt;
		now->mx = val;
		ch[++tot] = now->mx;
		df[tot] = now->cnt; 
		return ;
	}
	down(now);
	IntervalMin(now->l,st,ed,val);
	IntervalMin(now->r,st,ed,val);
	update(now);
}
set<int> S,T;
long long ans[maxn] = {0}; 
inline void solve(int X) {
	S.clear();
	T.clear();
	A.clr();
	B.clr();
	A.tot = B.tot = n;
	if (X == 0)
		Build(root,1,n);
	else
		clr(root);
	S.insert(p[1]);
	T.insert(p[1]-1);
	A.Add(p[1],1);
	B.Add(p[1]-1,-1);
	B.Add(p[1],1);
	int mx = p[1];
	ins(root,p[1],p[1]);
	
	long long tmp = 1;
	ans[1] += 1;
	set<int>::iterator it;
	for (int i = 2;i <= n; i++) {
		tot = 0;
		A.Add(p[i],1);
		tmp += B.getsum(p[i]-1,n);
		it = S.lower_bound(p[i]);
		if (it == S.end()) {
			ins(root,*S.begin(),p[i]);
			mx = p[i];
		}
		else if (it == S.begin()) {
			ins(root,p[i],mx);
			B.Add(p[i]-1,-1);
			T.insert(p[i]-1);	}
		else {
			ins(root,*it,mx);
			if (T.find(*it-1) == T.end()) {
				B.Add(*it-1,-1);
				tmp -= A.getsum(0,*it-1);
				T.insert(*it-1); 
			} 
			it--;
			IntervalMin(root,1,p[i]-1,*it);
			ins(root,*S.begin(),mx);
		}
		int ttt;
		for (int j = 1;j <= tot; j++) {
			ttt = A.getsum(0,ch[j]);
			tmp += (long long)ttt*df[j];
			B.Add(ch[j],df[j]);
		}
		S.insert(p[i]);
		ans[i] += tmp;
	}
}
int main() {
	scanf("%d",&n);
	for (int i = 1;i <= n; i++) {
		scanf("%d",&a[i]);
		p[a[i]] = i;
	}
	solve(0);
	for (int i = 1;i <= n; i++) {
		p[i] = n+1-p[i];
	}
	solve(1);
	for (int i = 1;i <= n; i++) {
		ans[i] -= i;
		printf("%I64d\n",ans[i]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值