CF2204F 思路分享(数学,单调栈)

https://codeforces.com/problemset/problem/2204/F

题意概述

对于长度为 \(n\) 的序列 \(b\),定义 \(MSF(b,k)\) 如下:

  • 初始元素为 \(\frac{1}{b_1},\frac{1}{b_2},\cdots,\frac{1}{b_n}\).

  • 进行恰好 \(k\) 次操作,每次操作选择任意一个元素,假设该元素为 \(\frac{x}{y}\),将 \(x+1\) 或将 \(y-1\)\(y \gt 1\)).

  • 最终得到所有元素之和的最大值即为 \(MSF(b,k)\).

给定长度为 \(n\) 的序列 \(a\) 和长度为 \(m\) 的序列 \(K\),对于每个 \(K_i\),求

\[\sum_{l=1}^{n}{\sum_{r=l}^{n}{MSF(a[l\cdots r],K_i)}} \]

\(998244353\).

\(1\le n,m \le 5\cdot 10^5\).

思路

对于某个 \(a\),假设对它进行 \(k\) 次操作,令

\[f(x)=\frac{1+k-x}{a-x}=1+\frac{1+k-a}{a-x} \]

\(0\le x \le \min(a-1,k)\),想要最大化 \(f(x)\).

  • \(k \ge a-1\) 时,\(f(x)\)\(x=a-1\) 处取最大值,\(f(x)_{max}=k-a+2\),此时操作为一直减分母.

  • \(k \lt a-1\) 时,\(f(x)\)\(x=0\) 处取最大值,\(f(x)_{max}=\frac{k+1}{a}\),此时操作为一直加分子.

因此得到了对单一元素进行 \(k\) 次操作的最优答案.

变化量

\[\Delta x = \begin{cases} k-a+2-\frac{1}{a},k\ge a-1 \\ \frac{k}{a},k \lt a-1 \end{cases} \]

下面证明始终对单一元素进行操作一定最优.

假设只对 \(a_1\)\(a_2\) 进行操作,\(a_1\) 进行 \(x\) 次,\(a_2\) 进行 \(k-x\) 次.

因为 \(\Delta x\) 两种情况都是关于操作次数的一次方程,将二者的变化量相加,得到的仍是关于操作次数的一次方程,在端点取到极值,因此等价于仅对其中一个元素进行操作.

数学归纳法可以证明,对更多元素同样如此.

观察 \(\Delta x\) 的表达,发现 \(a\) 都是越小越好,因此最终答案仅取决于数组中的最小元素,于是对于固定 \(k\),变成了经典问题.

对每个 \(i\),使用单调栈求出左边第一个小于 \(a_i\) 的位置 \(left_i\) 和右边第一个大于等于 \(a_i\) 的位置 \(right_i\).

每个 \(i\) 的贡献为 \((i-left_i)\cdot(right_i-i)\cdot \Delta x\).

对于不同的 \(k\),因为 \(\Delta x\) 是一次函数,可以分别维护两个系数.

\(a\) 索引排序,分别预处理 \(\Delta x\) 取第一种情况的前缀贡献和,以及取第二种情况的后缀贡献和.

处理每个 \(K_i\) 时,二分找到分界点即可.

时间复杂度 \(\mathcal{O}((n+m)\log n)\).

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int MOD = 998244353;

ll qpow(ll a,ll b){
	ll res = 1;
	while (b){
		if (b&1){
			res = res*a%MOD;
		}
		a = a*a%MOD;
		b >>= 1;
	}

	return res;
}

void solve(){
	int n,m;
	cin >> n >> m;

	vector<ll> a(n+1);
	for (int i=1;i<=n;i++){
		cin >> a[i];
	}

	vector<ll> K(m+1);
	for (int i=1;i<=m;i++){
		cin >> K[i];
	}

	vector<int> left(n+1),right(n+1);
	stack<int> stk;
	for (int i=1;i<=n;i++){
		while (!stk.empty() && a[i]<=a[stk.top()]){
			stk.pop();
		}
		left[i] = stk.empty()?0:stk.top();
		stk.push(i);
	}

	while (!stk.empty()){
		stk.pop();
	}

	for (int i=n;i>=1;i--){
		while (!stk.empty() && a[i]<a[stk.top()]){
			stk.pop();
		}
		right[i] = stk.empty()?n+1:stk.top();
		stk.push(i);
	}

	ll sum = 0;
	for (int i=1;i<=n;i++){
		sum = (sum+(ll)i*(n-i+1)%MOD*qpow(a[i],MOD-2)%MOD)%MOD;
	}

	vector<int> ord(n+1);
	iota(ord.begin(),ord.end(),0);
	sort(ord.begin()+1,ord.end(),[&](int i,int j){
		return a[i]<a[j];
	});

	vector<array<ll,2>> pre(n+1,{0,0});
	for (int i=1;i<=n;i++){
		pre[i][0] = (pre[i-1][0]+(ll)(ord[i]-left[ord[i]])*(right[ord[i]]-ord[i])%MOD)%MOD;
		ll temp = ((2-a[ord[i]]+MOD)%MOD-qpow(a[ord[i]],MOD-2)+MOD)%MOD;
		temp = (ll)(ord[i]-left[ord[i]])*(right[ord[i]]-ord[i])%MOD*temp%MOD;
		pre[i][1] = (pre[i-1][1]+temp)%MOD;
	}	

	vector<array<ll,2>> suf(n+2,{0,0});
	for (int i=n;i>=1;i--){
		suf[i][0] = (suf[i+1][0]+(ll)(ord[i]-left[ord[i]])*(right[ord[i]]-ord[i])%MOD*qpow(a[ord[i]],MOD-2)%MOD)%MOD;
	}

	for (int i=1;i<=m;i++){
		int l=1,r=n;
		while (l<=r){
			int mid = l+r >> 1;
			if (a[ord[mid]]<=K[i]+1){
				l = mid+1;
			}
			else{
				r = mid-1;
			}
		}

		ll c1 = (pre[l-1][0]+suf[l][0])%MOD;
		ll c2 = (pre[l-1][1]+suf[l][1])%MOD;
		cout << (sum+(c1*K[i]%MOD+c2)%MOD)%MOD << '\n';
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	// cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-06-14 20:22  kzssCCC  阅读(10)  评论(0)    收藏  举报