【前缀和 期望】P7875 「SWTR-7」IOI 2077|普及+

本文涉及知识点

期望
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

P7875 「SWTR-7」IOI 2077

题目背景

友情提醒:本题输入输出量很大,请不要使用 cin 或 scanf。题目最下方附有快读及其使用方法。
赛时提醒:若对于选出的 m m m 无解,则期望值为 0 0 0。可以结合样例 2 的解释说明以更好理解。
赛时提醒:你需要求的是能力值之和的期望而不是最大值。

小 A 被 FCC 钦定参加 IOI 2077!71 岁老将请求出战!

题目描述

IOI 2077 有 n n n 位候选 参赛者,他们分别编号为 1 ∼ n 1\sim n 1∼n。每位候选参赛者都有一个能力值,且能力值互不相等 ,第 i i i 位候选参赛者的能力值为 a i a_i ai。小 A 更喜欢有序的数字,所以他将这 n n n 位候选参赛者按照能力值从小到大 排好了序,即满足 a i < a i + 1 ( 1 ≤ i < n ) a_i<a_{i+1}\ (1\leq i<n) ai<ai+1 (1≤i<n)。

正式参赛者将会从这 n n n 位候选参赛者中产生。具体地,所有参赛者将是候选参赛者的一个子串 [ l , r ] [l,r] [l,r],即编号为 l , l + 1 , ⋯   , r l,l+1,\cdots,r l,l+1,⋯,r 的选手将参加 IOI 2077,其中,小 A 的编号为 k k k。因为他知道自己被钦定参加 IOI 2077,所以 l ≤ k ≤ r l\leq k\leq r l≤k≤r。可能的参赛者一共有 q q q 种情况,每种情况用三个数 l i , r i , k i ( l i ≤ k i ≤ r i ) l_i,r_i,k_i\ (l_i\leq k_i\leq r_i) li,ri,ki (li≤ki≤ri) 描述,即参赛者为编号在区间 [ l i , r i ] [l_i,r_i] [li,ri] 中的候选参赛者,而小 A 的编号为 k i k_i ki。

由于自己太菜,小 A 对即将到来的 IOI 感到力不从心。他决定选择一些参赛者作为队友,并与他们在赛场上相互帮(zuo)助(bi)。具体地,设正式参赛人数为 s s s,那么小 A 会在 [ 0 , ⌊ s − 1 2 ⌋ ] [0,\lfloor\frac{s-1}{2}\rfloor] [0,⌊2s−1⌋] 中等概率随机 选择一个数 m m m,并从 s s s 位参赛者中随机 选出 2 m 2m 2m 个作为他的队友。不过,小 A 不希望自己显得太菜,所以他的能力值 a k a_k ak 必须是这 2 m + 1 2m+1 2m+1 个人的能力值的中位数

俗话说,人多力量大,小 A 希望他与所有选出的队友的能力值之和尽量地大。不过在此之前,他想知道这个值的期望值是多少 。请对 998244353 998244353 998244353 取模,保证答案在该模数下有意义。对于每一种可能的参赛者情况,你都需计算该情况下的答案。为了避免过大的输出,你只需要计算所有答案的异或和。

输入格式

第一行一个整数 t t t,表示该测试点 Subtask 编号。

第二行两个整数 n , q n,q n,q,分别表示候选参赛者个数和情况总数。

第三行 n n n 个整数 a 1 , a 2 , ⋯   , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an 表示每个候选参赛者的能力值。保证 a i a_i ai 递增。

接下来 q q q 行,每行三个整数 l i , r i , k i l_i,r_i,k_i li,ri,ki 描述一个可能的参赛者情况。

输出格式

输出一行一个整数,表示所有答案的异或和。

输入输出样例 #1

输入 #1

复制代码
0
5 2
2 3 5 7 8
1 5 3
2 4 2

输出 #1

复制代码
499122189

说明/提示

「样例 1 说明」

  • 第 1 个询问:

    因为 s 1 = r 1 − l 1 + 1 = 5 s_1=r_1-l_1+1=5 s1=r1−l1+1=5,所以 m m m 可以为 0 , 1 0,1 0,1 或 2 2 2。
    m = 0 m=0 m=0 时:小 A 没有队友,那么期望值就是他自身的能力值 a k 1 = a 3 = 5 a_{k_1}=a_3=5 ak1=a3=5。
    m = 1 m=1 m=1 时:小 A 可以选编号 ( 1 , 4 ) (1, 4) (1,4) 或 ( 1 , 5 ) (1, 5) (1,5) 或 ( 2 , 4 ) (2, 4) (2,4) 或 ( 2 , 5 ) (2, 5) (2,5) 的参赛者作为他的队友,能力值之和分别为 14 , 15 , 15 , 16 14,15,15,16 14,15,15,16,期望值为 14 + 15 + 15 + 16 4 = 15 \frac{14+15+15+16}{4}=15 414+15+15+16=15。
    m = 2 m=2 m=2 时:小 A 只能全选,期望值为 2 + 3 + 5 + 7 + 8 = 25 2+3+5+7+8=25 2+3+5+7+8=25。

    综上,期望值为 5 + 15 + 25 3 = 15 \frac{5+15+25}{3}=15 35+15+25=15。

  • 第 2 个询问:

    因为 s 2 = r 2 − l 2 + 1 = 3 s_2=r_2-l_2+1=3 s2=r2−l2+1=3,所以 m m m 可以为 0 0 0 或 1 1 1。
    m = 0 m=0 m=0 时,小 A 没有队友,期望值为 3 3 3。
    m = 1 m=1 m=1 时,小 A 无法选择,期望值为 0 0 0。

    综上,期望值为 3 + 0 2 = 3 2 \frac{3+0}{2}=\frac{3}{2} 23+0=23,对 998244353 998244353 998244353 取模后为 499122178 499122178 499122178。

15 ⊕ 499122178 = 499122189 15\oplus499122178=499122189 15⊕499122178=499122189。

「数据范围与约定」

本题采用捆绑测试。

记 s i = r i − l i + 1 s_i=r_i-l_i+1 si=ri−li+1。

  • Subtask #0(1 point):是样例。
  • Subtask #1(10 points): s i ≤ 2 s_i\leq 2 si≤2。
  • Subtask #2(20 points): s i ≤ 16 s_i\leq 16 si≤16, q ≤ 40 q\leq 40 q≤40, n ≤ 640 n\leq 640 n≤640。
  • Subtask #3(15 points): s i , q ≤ 500 s_i,q\leq 500 si,q≤500, n ≤ 10 5 n\leq 10^5 n≤105。
  • Subtask #4(15 points): s i , q ≤ 3 × 10 3 s_i,q\leq 3\times 10^3 si,q≤3×103, n ≤ 10 5 n\leq 10^5 n≤105。
  • Subtask #5(15 points): s i , q ≤ 2 × 10 5 s_i,q\leq 2\times 10^5 si,q≤2×105, n ≤ 5 × 10 5 n\leq 5\times 10^5 n≤5×105。
  • Subtask #6(24 points):无特殊限制。

对于 100 % 100\% 100% 的数据, 1 ≤ n , q ≤ 2 × 10 6 1\leq n,q\leq 2\times 10^6 1≤n,q≤2×106, 1 ≤ l i ≤ k i ≤ r i ≤ n 1\leq l_i\leq k_i\leq r_i\leq n 1≤li≤ki≤ri≤n, 1 ≤ a i ≤ 998244352 1 \le a_i \le 998244352 1≤ai≤998244352, a i < a i + 1 ( 1 ≤ i < n ) a_i<a_{i+1}\ (1\leq i<n) ai<ai+1 (1≤i<n)。

对于所有测试点,时间限制 1s,空间限制 512MB。

「帮助/提示」

关于 有理数取余中位数

本题输入输出量极大请注意 I/O 优化。

本题提供有符号 32 位整数快读模板,保证读入用时不超过 250ms:

cpp 复制代码
#define gc getchar()
inline int read(){
	int x=0; bool sgn=0; char s=gc;
	while(!isdigit(s))sgn|=s=='-',s=gc;
	while(isdigit(s))x=(x<<1)+(x<<3)+(s-'0'),s=gc;
	return sgn?-x:x;
}

// 如果需要读入直接调用 read() 即可。
// 一个例子(与正解无关,仅供参考):

int t=read(),n=read(),q=read();
int a[2000005],l[2000005],r[2000005],k[2000005];
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=q;i++)l[i]=read(),r[i]=read(),k[i]=read();

// 这样你就可以在 250ms 内读入全部数据了。

「题目来源」

Sweet Round 07 C。

idea & solution:SSerWarriors_Cat;data:Alex_Wei ;验题:chenxia25


IOI 2077 落下帷幕,小 A 凭借出(dui)色(you)的发(bang)挥(zhu)成功 AK 了 IOI,这不禁让他回想起曾经满腔热血的自己,以及和他共同奋斗在 OI 路上的战友们。如今他们虽已天各一方,说起来也有十几年没见过面了,但他们真挚的友谊未曾淡去,也将永远不会褪色。

"爷爷,您手机里有段录音,还写着 'ycx txdy!'。"
"哦,是嘛?放出来听听。"

"I AK IOI. I AK ACM World Final. I AK Universe OI. I think all of you are vegetable chickens."
"I AK IOI. I AK ACM World Final. I AK Universe OI. I think all of you are vegetable chickens."
"I AK IOI. I AK ACM World Final. I AK Universe OI. I think all of you ..."

2077.7.7

期望

令左闭右开空间 [ l i , k i ) [l_i,k_i) [li,ki)的长度L,即实力比主角弱的数量。

令左开右闭空间 ( k i , r i ] (k_i,r_i] (ki,ri]的长度R,即实力比主角弱的数量。

m=0时,左空间任意元素被选中的几率0。

m=1时,左空间任意元素被选中的几率 1 L \frac1{L} L1

m=2时,左空间任意元素被选中的几率 2 L \frac2{L} L2,第二个数选中此元素的几率 L − 1 L × 1 L − 1 = 1 L \frac{L-1}{L} \times \frac1{L-1}=\frac1{L} LL−1×L−11=L1,总几率 2 L \frac2{L} L2

m=3时,左空间任意元素被选中的几率 3 L \frac3{L} L3,第三个数选中此元素的几率为 L − 1 L × L − 2 L − 1 × 1 L − 2 \frac{L-1}{L} \times \frac{L-2}{L-1} \times \frac1{L-2} LL−1×L−1L−2×L−21
⋮ \vdots ⋮

第个数被选中的此元素的几率为: L − 1 L × L − 2 L − 1 × ⋯ L − i + 1 L − i + 2 1 L + 1 − i = 1 N \frac{L-1}{L} \times \frac{L-2}{L-1} \times \cdots \frac{L-i+1}{L-i+2} \frac 1{L+1-i }=\frac1N LL−1×L−1L−2×⋯L−i+2L−i+1L+1−i1=N1

对于 ∀ \forall ∀m,任意左区间元素,被选中的概率: m L \frac m {L} Lm

MM = ⌊ s i − 1 2 ⌋ = ⌊ r i − l i 2 ⌋ \lfloor \frac{s_i-1}2 \rfloor =\lfloor \frac{r_i-l_i}2 \rfloor ⌊2si−1⌋=⌊2ri−li⌋
m ∈ [ 0 , M ] m\in[0,M] m∈[0,M],M=min(L,R) m取值概率相同,故对于所有m,任意左区间元素被选中的概率:
x l = ∑ i : 0 M i L ( M M + 1 ) = M × ( M + 1 ) / 2 L ( M M + 1 ) xl=\frac{ \sum_{i:0}^Mi}{L(MM +1)}=\frac{M \times (M+1) /2 }{L(MM+1)} xl=L(MM+1)∑i:0Mi=L(MM+1)M×(M+1)/2

令S 左区间之和:则左区间队友,期望和是: x l × S xl \times S xl×S

右区间之和是T,概率是xr,则结果是 x l × S + x r × T + a [ k ] ∗ ( M + 1 ) / ( M M + 1 ) xl \times S + xr \times T + a[k]*(M+1)/(MM+1) xl×S+xr×T+a[k]∗(M+1)/(MM+1)

如果不预处理逆元,则时间复杂度 :O(nlogn)

线性处理逆元后,时间复杂度:O(n)

代码

核心代码

cpp 复制代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include<array>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
	in >> pr.first >> pr.second;
	return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t);
	return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
	return in;
}

template<class T = int>
vector<T> Read() {
	int n;
	cin >> n;
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
	vector<T> ret;
	T tmp;
	while (cin >> tmp) {
		ret.emplace_back(tmp);
		if ('\n' == cin.get()) { break; }
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<int N = 1'000'000>
class COutBuff
{
public:
	COutBuff() {
		m_p = puffer;
	}
	template<class T>
	void write(T x) {
		int num[28], sp = 0;
		if (x < 0)
			*m_p++ = '-', x = -x;

		if (!x)
			*m_p++ = 48;

		while (x)
			num[++sp] = x % 10, x /= 10;

		while (sp)
			*m_p++ = num[sp--] + 48;
		AuotToFile();
	}
	void writestr(const char* sz) {
		strcpy(m_p, sz);
		m_p += strlen(sz);
		AuotToFile();
	}
	inline void write(char ch)
	{
		*m_p++ = ch;
		AuotToFile();
	}
	inline void ToFile() {
		fwrite(puffer, 1, m_p - puffer, stdout);
		m_p = puffer;
	}
	~COutBuff() {
		ToFile();
	}
private:
	inline void AuotToFile() {
		if (m_p - puffer > N - 100) {
			ToFile();
		}
	}
	char  puffer[N], * m_p;
};

template<int N = 1'000'000>
class CInBuff
{
public:
	inline CInBuff() {}
	inline CInBuff<N>& operator>>(char& ch) {
		FileToBuf();
		while (('\r' == *S) || ('\n' == *S) || (' ' == *S)) { S++; }//忽略空格和回车
		ch = *S++;
		return *this;
	}
	inline CInBuff<N>& operator>>(int& val) {
		FileToBuf();
		int x(0), f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		val = f ? -x : x; S++;//忽略空格换行		
		return *this;
	}
	inline CInBuff& operator>>(long long& val) {
		FileToBuf();
		long long x(0); int f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		val = f ? -x : x; S++;//忽略空格换行
		return *this;
	}
	template<class T1, class T2>
	inline CInBuff& operator>>(pair<T1, T2>& val) {
		*this >> val.first >> val.second;
		return *this;
	}
	template<class T1, class T2, class T3>
	inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
		*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
		return *this;
	}
	template<class T1, class T2, class T3, class T4>
	inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
		*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
		return *this;
	}
	template<class T = int>
	inline CInBuff& operator>>(vector<T>& val) {
		int n;
		*this >> n;
		val.resize(n);
		for (int i = 0; i < n; i++) {
			*this >> val[i];
		}
		return *this;
	}
	template<class T = int>
	vector<T> Read(int n) {
		vector<T> ret(n);
		for (int i = 0; i < n; i++) {
			*this >> ret[i];
		}
		return ret;
	}
	template<class T = int>
	vector<T> Read() {
		vector<T> ret;
		*this >> ret;
		return ret;
	}
private:
	inline void FileToBuf() {
		const int canRead = m_iWritePos - (S - buffer);
		if (canRead >= 100) { return; }
		if (m_bFinish) { return; }
		for (int i = 0; i < canRead; i++)
		{
			buffer[i] = S[i];//memcpy出错			
		}
		m_iWritePos = canRead;
		buffer[m_iWritePos] = 0;
		S = buffer;
		int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);
		if (readCnt <= 0) { m_bFinish = true; return; }
		m_iWritePos += readCnt;
		buffer[m_iWritePos] = 0;
		S = buffer;
	}
	int m_iWritePos = 0; bool m_bFinish = false;
	char buffer[N + 10], * S = buffer;
};

template<long long MOD = 1000000007, class T1 = int, class T2 = long long>
class C1097Int
{
public:
	C1097Int(T1 iData = 0) :m_iData(iData% MOD)
	{

	}
	C1097Int(T2 llData) :m_iData(llData% MOD) {

	}
	C1097Int  operator+(const C1097Int& o)const
	{
		return C1097Int(((T2)m_iData + o.m_iData) % MOD);
	}
	C1097Int& operator+=(const C1097Int& o)
	{
		m_iData = ((T2)m_iData + o.m_iData) % MOD;
		return *this;
	}
	C1097Int& operator-=(const C1097Int& o)
	{
		m_iData = ((T2)MOD + m_iData - o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator-(const C1097Int& o)const
	{
		return C1097Int(((T2)MOD + m_iData - o.m_iData) % MOD);
	}
	C1097Int  operator*(const C1097Int& o)const
	{
		return((T2)m_iData * o.m_iData) % MOD;
	}
	C1097Int& operator*=(const C1097Int& o)
	{
		m_iData = ((T2)m_iData * o.m_iData) % MOD;
		return *this;
	}
	C1097Int  operator/(const C1097Int& o)const
	{
		return *this * o.PowNegative1();
	}
	C1097Int& operator/=(const C1097Int& o)
	{
		*this /= o.PowNegative1();
		return *this;
	}
	bool operator==(const C1097Int& o)const
	{
		return m_iData == o.m_iData;
	}
	bool operator<(const C1097Int& o)const
	{
		return m_iData < o.m_iData;
	}
	C1097Int pow(T2 n)const
	{
		C1097Int iRet = (T1)1, iCur = *this;
		while (n)
		{
			if (n & 1)
			{
				iRet *= iCur;
			}
			iCur *= iCur;
			n >>= 1;
		}
		return iRet;
	}
	C1097Int PowNegative1()const
	{
		return pow(MOD - 2);
	}
	T1 ToInt()const
	{
		return ((T2)m_iData + MOD) % MOD;
	}
private:
	T1 m_iData = 0;;
};

typedef C1097Int<998244353> BI;
class Solution {
public:
	const int MOD = 998244353;
	int Ans(const int N, const vector<int>& a, vector<tuple<int, int, int>>& lrk) {
		vector<BI> preSum(1);
		for (const auto& i : a) {
			preSum.emplace_back(preSum.back() + i);
		}
		const int M = N + 2;
		vector<int> inv(M + 1, 1);
		for (int i = 2; i <= M; ++i) {
			inv[i] = (long long)(MOD - MOD / i) * inv[MOD % i] % MOD;
		}
		int ans = 0;
		for (auto [l, r, k] : lrk) {
			l--, r--, k--;
			const int L = k - l;
			const int R = r - k;
			const int M = min(L, R);
			const int MM = (r - l) / 2;
			const auto MM2 = BI(M) * BI(M + 1) * inv[2] * inv[MM + 1];
			const auto S = preSum[k] - preSum[l];
			const auto T = preSum[r + 1] - preSum[k + 1];
			const auto cur = MM2 * S * inv[L] + MM2 * T * inv[R] + BI(M + 1) * inv[MM + 1] * a[k];
			ans ^= cur.ToInt();
		}
		return ans;
	}
};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	ios::sync_with_stdio(0); cin.tie(nullptr);
	CInBuff<> in; COutBuff<10'000'000> ob;	
	int T,N,Q;
	in >> T>> N >> Q;
	auto a = in.Read(N);
	auto lrk = in.Read<tuple<int, int, int>>(Q);
#ifdef _DEBUG		
		//printf("N=%d", N);
		Out(lrk, ",lrk=");
		Out(a, ",a=");
		//Out(ab, ",ab=");
		//Out(par, "par=");
		//Out(que, "que=");
		//Out(B, "B=");
#endif // DEBUG		
	auto res = Solution().Ans(a.size(),a,lrk);
	cout << res << "\n";
	return 0;
};

单元测试

cpp 复制代码
	vector<int>a;
		vector<tuple<int, int, int>> lrk;
		TEST_METHOD(TestMethod01)
		{
			lrk = { {1,5,3},{2,4,2} }, a = { 2,3,5,7,8 };
			auto res = Solution().Ans(a.size(),a,lrk);
			AssertEx(499122189, res);
		}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法用**C++**实现。

相关推荐
leaves falling2 小时前
迭代实现 斐波那契数列
数据结构·算法
珂朵莉MM2 小时前
全球校园人工智能算法精英大赛-产业命题赛-算法巅峰赛 2025年度画像
java·人工智能·算法·机器人
Morwit2 小时前
*【力扣hot100】 647. 回文子串
c++·算法·leetcode
天赐学c语言3 小时前
1.7 - 删除排序链表中的重要元素II && 哈希冲突常用解决冲突方法
数据结构·c++·链表·哈希算法·leecode
w陆压3 小时前
12.STL容器基础
c++·c++基础知识
tobias.b3 小时前
408真题解析-2009-13-计组-浮点数加减运算
算法·计算机考研·408考研·408真题
菜鸟233号3 小时前
力扣96 不同的二叉搜索树 java实现
java·数据结构·算法·leetcode
龚礼鹏4 小时前
Android应用程序 c/c++ 崩溃排查流程二——AddressSanitizer工具使用
android·c语言·c++
Coovally AI模型快速验证4 小时前
超越Sora的开源思路:如何用预训练组件高效训练你的视频扩散模型?(附训练代码)
人工智能·算法·yolo·计算机视觉·音视频·无人机
千金裘换酒4 小时前
Leetcode 有效括号 栈
算法·leetcode·职场和发展