P6375 「StOI-1」小Z的旅行 题解

题意:P6375 「StOI-1」小 Z 的旅行

给定一座山,每座山有一个高度,只能向更低的山走或者向高度相同的山走,要求不能向高度相同的山连续走两次,不能原地不动。
每次走的权值都是两座山之间的坐标差的绝对值。走的山会从所有可以走的山中等概率随机选。求从最高的山到最低的山的期望权值。保证最高与最低的山唯一。

思路

看到期望,显然考虑 DP。
发现从高到低不好弄,因此考虑从最低的山依次向最高的山转移。这样我们显然先将点按山的高度从小到大排序,然后按顺序依次考虑。

由于有不能连续走的要求,因此我们设 \(f_{i,0/1}\) 表示从第 \(i\) 个点开始,能否再连续走高度相同的山,走到高度最低的山的期望权值。其中 \(f_{i,0}\) 表示不能再连续走,\(f_{i,1}\) 反之。

然后发现 \(f_{i,0}\) 只能从比其低的山转移而来,而转移后就一定可以再连续走,因此有转移:

\[f_{i,0}=\frac{\sum_{h_j<h_i} f_{j,1}+|x_i-x_j|}{\sum_j [h_j<h_i]} \]

其中 \(x_i\) 表示排完序后第 \(i\) 个点在序列中的原坐标
由于我们已经按高度排序,因此发现这就是一个前缀和而已,高度比 \(h_i\) 小的点数也是好求的。因此我们设 \(num0_i\) 表示高度低于 \(h_i\) 的点数。

然后对于 \(f_{i,1}\),其有概率走到高度比 \(h_i\) 低的点,这时同样也是从 \(f_{j,1}\) 转移而来,也有概率走到比 \(h_i\) 高的点,这时由于不能连续走因此就要从 \(f_{j,0}\) 转移而来。
注意还要除以总的方案数。我们同样定义一个 \(num1_i\) 表示高度小于等于 \(h_i\) 的点数。综上,有转移:

\[f_{i,1}=\frac{(\sum_{h_j<h_i} f_{j,1}+|x_i-x_j|) + (\sum_{h_j=h_i}f_{j,0}+|x_i-x_j|)}{num1_i} \]

可以发现分子上第一个括号的形式与 \(f_{i,0}\) 是一样的,因此可以化简一下。注意要将选到比 \(h_i\) 低的情况数乘上。化简后有转移:

\[f_{i,1}=\frac{f_{i,0}\times num0_i + (\sum_{h_j=h_i}f_{j,0}+|x_i-x_j|)}{num1_i} \]

可以发现对于两个转移,我们都可以将 \(f_j,0/1\) 与绝对值的计算分开处理。显然排序后对于 \(f\) 的求和仍然是前缀和就可以解决的。
然后我们就只需要处理距离的绝对值了。由于整个序列都不会变,因此我们可以将这个距离预处理出来。

我们可以通过树状数组从左到右扫一遍来做。具体而言,应另一篇题解的提醒,可以去做这种处理绝对值的 模板题
需要注意的是,我么需要处理两种权值,一种是到所有 \(h_j<h_i\)\(j\) 的权值,另一种是到所有 \(h_j=h_i\)\(j\) 的权值,我们分别记为 \(dis0_i\)\(dis1_i\)

code

实现的话我没有用离散化,而是将所有 \(h\) 相同的点一起转移。个人感觉这样思路比较清晰。
具体看代码吧。代码略有压行。(?)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353,N=5e5+7;
int n,dis0[N],dis1[N],num0[N],num1[N],f[N][2],tmpsum[N],tmpcnt[N];
struct node{int x,h;}a[N];
struct edge{  //树状数组 
	int tr[N];
	void modify(int x,int w){while(x<=N-5)(tr[x]+=w)%=p,x+=x&(-x);}
	int query(int x){int res=0;while(x) (res+=tr[x])%=p,x-=x&-x;return res;}
}sum,cnt;  //分别记前缀坐标的和以及前缀坐标的个数 
bool cmp(node x,node y){return x.h<y.h;}
int ksm(int x,int k){
	int res=1;x%=p;
	while(k){if(k&1) res=res*x%p;x=x*x%p,k>>=1;}
	return res;
}
#define inv(x) (ksm(x,p-2))
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;for(int i=1,h;i<=n;i++) cin>>h,a[i]={i,h};
	sort(a+1,a+n+1,cmp);int tmp=0;
	for(int l=1;l<=n;){
		int r=l,z=0;
		while(a[r].h==a[r+1].h) ++r; //注意到高度小于的点数就是 l-1,等于的点数就是 r-l。(注意不能走到自己) 
		for(int i=l;i<=r;i++){
			int sum0=sum.query(a[i].x)%p,cnt0=cnt.query(a[i].x);tmpsum[i]=sum0,tmpcnt[i]=cnt0;
			num0[i]=l-1;dis0[i]=(cnt0*a[i].x%p-sum0 + (tmp-sum0-(l-1-cnt0)*a[i].x%p)+p)%p;//坐标小的与大的分开计算 
		}
		for(int i=l;i<=r;i++) sum.modify(a[i].x,a[i].x),cnt.modify(a[i].x,1),z+=a[i].x;  
		//先求出严格小于的再将权值相同的插入进树状数组,同时统计权值相同的权值和 
		for(int i=l;i<=r;i++){
			int sum1=(sum.query(a[i].x-1)%p-tmpsum[i]+p)%p,cnt1=(cnt.query(a[i].x-1)-tmpcnt[i]+p)%p;//这里将总的减去之前记录的高度小于的就是高度等于的信息 
			num1[i]=r-1;dis1[i]=(cnt1*a[i].x%p-sum1 + z-a[i].x-sum1-(r-l-cnt1)*a[i].x+p)%p;
		}
		l=r+1;tmp+=z;
	}
	f[1][0]=f[1][1]=0;int tmp0=0,tmp1=0;
	for(int l=2;l<=n;){
		int r=l,z=0,y=0;while(a[r].h==a[r+1].h) ++r;
		for(int i=l;i<=r;i++) f[i][0]=(tmp1+dis0[i])%p*inv(l-1)%p,z=(z+f[i][0])%p; 
		for(int i=l;i<=r;i++) f[i][1]=(f[i][0]*(l-1)%p*inv(r-1)%p+(z-f[i][0]+dis1[i]+p)%p*inv(r-1)%p)%p,y=(y+f[i][1])%p;
		(tmp0+=z)%=p,(tmp1+=y)%=p;
		l=r+1;
	}
	cout<<f[n][0]<<'\n';return 0;
	return 0;
}
posted @ 2025-04-11 20:43  all_for_god  阅读(33)  评论(0)    收藏  举报