本文涉及知识点
期望
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 内读入全部数据了。
「题目来源」
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++**实现。