F. Starlight 简解——2024年“核桃杯”武汉地区ACM萌新赛

F. Starlight 简解

给定 \(1\sim n\) 的排列 \(a_1,\dots,a_n\)。每天取地面上最左石块放到塔顶;若塔顶石块大于塔中所有其他石块,则塔倒塌,所有石块等概率随机回到地面最左侧;否则继续,直到所有石块在塔中。求成功摘星的期望天数(整数,模 \(10^9+7\))。

\(q\) 次交换:每次交换序列中的 \(a_x\)\(a_y\),事件不独立,序列持续修改。输出初始及每次交换后的答案。\(1\le n,q\le2\times10^5\)

感觉是一道非常 educational 的题目,前一部分的详细证明可以看官方题解,此处给出线段树的实现方式。

首先如果 \(a_i>\min_{j=1}^{i-1} a_j\),则到了 \(i\) 这个位置塔必定要倒塌,此时前 \(i\) 个数随机排列;如果 \(a_i<\min_{j=1}^{i-1}a_j\) 则继续考虑 \(i+1\) 即可。我们定义 \(f(i)\) 为前 \(i\) 个数随机排列的情况下,前 \(i\) 个数能搭成一个高度为 \(i\) 的塔的期望步数。最后的总答案即为 \(n+\sum f(i) [a_i>\min_{j=1}^{i-1}a_j]\)

只有 \(a_i<\min_{j=1}^{i-1}a_j\) 时才可能搭成高为 \(i\) 的塔,最小值分配在 \(a_i\) 的概率是 \(\frac{i-1}{i}\),可以得到递推关系

\[f(i)=f(i-1)+1+\frac{i-1}{i}f(i) \implies\\ f(i)=i\times[f(i-1)+1] \]

其中 \(f(1)=1\)


于是我们变成了求所有 \(a_i>\min_{j=1}^{i-1} a_j\) 位置的 \(f(i)\) 之和,我这里按照题解转化为前缀最小值位置的权值和,即 \(a_i<\min_{j=1}^{i-1} a_j\) 位置的 \(f(i)\) 之和,最后用 \(\sum_{i=1}^{n}f(i)+n\) 减去这个权值和即为答案。

这个东西我一开始想用线段树套线段树做,实际上有只用一颗线段树的简单做法。

具体而言我们对于线段树上的节点 \(p\),它管辖的范围为 \([l,r]\),记录 \(minv_p\) 表示范围内的最小值,\(sum_p\)\([l,r]\) 的数组成的新的序列中前缀最小值的位置的 \(f(i)\) 权值和,即

\[sum_p=\sum_{i=l}^{r}f(i)[a_i<\min_{j=l}^{i-1} a_j] \]

注意当 \(i=l\) 时我们认为 \(\min_{j=l}^{i-1} a_j=\infty\),也就是说第一个数的 \(f(i)\) 总是可以取到。

于是我们便可以利用这两个信息在 \(O(\log n)\) 下查询在先前的最小值为 \(lim\) 的情况下,比 \(lim\) 的值小且在区间中也是前缀最小值的数的 \(f(i)\) 和。

//线段树核心函数,求解p的子树中满足 a_i \leq min{lim,a_l,...,a_{i-1}} 的位置的权值和
ll query(int p,ll lim){//保证了只递归 log 次
	if(len[p]==1)return minv[p]>=lim?0:sum[p];
	if(minv[ls]>=lim)return query(rs,lim);
	return (query(ls,lim)+sum[p]-sum[ls]+P)%P;
}

线段树上信息合并便可以得到

void pushup(int p){
	minv[p]=min(minv[ls],minv[rs]);
	sum[p]=(sum[ls]+query(rs,minv[ls]))%P;
}

总时间复杂度 \(O(n+q\log^2n)\),具体实现见代码。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int,int>ttfa;
const int N=200005;
const ll INF=0x3f3f3f3f3f3f3f3f;
const ll P=1000000007;

ll f[N],fsum=0;

int n,q,a[N];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
ll sum[N<<2];int minv[N<<2],len[N<<2];
//线段树核心函数,求解p的子树中满足 a_i \leq min{lim,a_l,...,a_{i-1}} 的位置的权值和
ll query(int p,ll lim){//保证了只递归 log 次
	if(len[p]==1)return minv[p]>=lim?0:sum[p];
	if(minv[ls]>=lim)return query(rs,lim);
	return (query(ls,lim)+sum[p]-sum[ls]+P)%P;
}
void pushup(int p){
	minv[p]=min(minv[ls],minv[rs]);
	sum[p]=(sum[ls]+query(rs,minv[ls]))%P;
}
void build(int p,int l,int r){
	len[p]=r-l+1;
	if(l==r){
		sum[p]=f[l];
		minv[p]=a[l];
		return;
	}
	build(ls,l,mid);build(rs,mid+1,r);
	pushup(p);
}
void update(int p,int l,int r,int L){
	if(l==r){
		minv[p]=a[L];
		return;
	}
	if(L<=mid)update(ls,l,mid,L);
	else update(rs,mid+1,r,L);
	pushup(p);
}

#undef ls
#undef rs
#undef mid

ll ans(){
	return (fsum+n-sum[1]+P)%P;
}

int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
	}
	fsum=f[1]=1;
	for(int i=2;i<=n;++i){
		f[i]=i*(f[i-1]+1)%P;
		fsum=(fsum+f[i])%P;
	}
	build(1,1,n);
	printf("%lld\n",ans());
	for(int T=1;T<=q;++T){
		int x,y;scanf("%d%d",&x,&y);
		swap(a[x],a[y]);
		update(1,1,n,x);
		update(1,1,n,y);
		printf("%lld\n",ans());
	}
	return 0;
}
posted @ 2026-09-18 00:31  BigSmall_En  阅读(8)  评论(0)    收藏  举报