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(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)\) 权值和,即
注意当 \(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;
}

浙公网安备 33010602011771号