洛谷 P6834 [Cnoi2020] 梦原 题解

Announcement

  • Programmed on 2024/2/25
  • Written on 2024/2/26

题目来源

Description

有一棵有 n n n 个节点、根节点编号为 1 1 1、形态不确定的树,且每个点都有个权值 a i a_i ai

对于任意节点 i ∈ [ 2 , n ] ∩ Z i\in[2,n]\cap\Z i[2,n]Z,其父亲将从 [ i − k , i − 1 ] ∩ N + [i-k,i-1]\cap\N^+ [ik,i1]N+ 中等概率选取一个。

当树的形态确定后,将进行若干次操作,每次操作可以选择节点权值均不为 0 0 0 的一个联通块,并将该联通块内每个节点的权值减去 1 1 1

求最少操作次数的期望值(   m o d   998244353 \bmod 998244353 mod998244353)。

  • 1 ≤ k < n ≤ 1 0 6 1\le k<n\le10^6 1k<n106

Solution

考虑树的形态确定时,最少的操作次数 a n s ans ans

类似于 洛谷 P1969 [NOIP2013 提高组] 积木大赛 的树上做法:
a n s = ∑ i = 1 n ∑ j = 1 min ⁡ ( i − 1 , k ) max ⁡ ( a i − a i − j , 0 ) min ⁡ ( i − 1 , k ) ans=\sum_{i=1}^n\frac{\sum_{j=1}^{\min(i-1,k)}\max(a_i-a_{i-j},0)}{\min(i-1,k)} ans=i=1nmin(i1,k)j=1min(i1,k)max(aiaij,0)
若暴力统计效率为 O ( n k ) O(nk) O(nk),需要优化。

观察发现,计算 ∑ j = 1 min ⁡ ( i − 1 , k ) max ⁡ ( a i − a i − j , 0 ) \sum\limits_{j=1}^{\min(i-1,k)}\max(a_i-a_{i-j},0) j=1min(i1,k)max(aiaij,0) 时存在复杂度瓶颈。

由于只有当 a i > a i − j a_i>a_{i-j} ai>aij 时才对答案有贡献,则该算式等价于 ∑ j = 1 min ⁡ ( i − 1 , k ) [ a i > a i − j ] a i − [ a i > a i − j ] a i − j \sum\limits_{j=1}^{\min(i-1,k)}[a_i>a_{i-j}]a_i-[a_i>a_{i-j}]a_{i-j} j=1min(i1,k)[ai>aij]ai[ai>aij]aij

那么,我们只需要统计所有权值小于 a i a_i ai 的节点个数,以及对应的节点权值和。

于是可以开两个树状数组分别统计满足条件的节点个数和权值和。

注意到若一个节点 x < i − k x<i-k x<ik 则需从树状数组中移除。

再加上离散化,复杂度可以达到 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Conclusion

  • 对于统计 i i i 的某些权值时只需统计满足 a j < a i a_j<a_i aj<ai j j j a j a_j aj 之和等类似问题,可以考虑树状数组优化。
  • 若只关心各权值的大小关系而不关心具体数值,考虑离散化技巧,进而避免 MLE / TLE 等。

Code

#include <bits/stdc++.h>
using namespace std;
const int p=998244353;
int n,k,a[1000005],b[1000005],f[1000005],c[2][1000005],tot; // c[0]-权值 c[1]-个数
unordered_map<int,int> id;
int qpow(int a,int b){
	int res=1;
	for (;b;b>>=1,a=1ll*a*a%p) if (b&1) res=1ll*res*a%p;
	return res;
}
void update(int op,int x,int d){
	for (;x<=tot;x+=x&(-x)) (c[op][x]+=d)%=p;
}
int query(int op,int x){
	int res=0;
	for (;x;x-=x&(-x)) (res+=c[op][x])%=p;
	return res;
}
int main(){
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
	sort(b+1,b+n+1);
	for (int i=1;i<=n;i++) if (i==n||b[i]!=b[i+1]) id[b[i]]=++tot;
	f[1]=a[1],update(0,id[a[1]],a[1]),update(1,id[a[1]],1);
	for (int i=2;i<=n;i++){
		update(0,id[a[i]],a[i]),update(1,id[a[i]],1);
		f[i]=(f[i-1]+1ll*qpow(min(i-1,k),p-2)*((1ll*query(1,id[a[i]]-1)*a[i]-query(0,id[a[i]]-1))%p+p)%p)%p;
		if (i-k>0) update(0,id[a[i-k]],-a[i-k]),update(1,id[a[i-k]],-1);
	}
	printf("%d\n",f[n]);
	return 0;
}
  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值