2024.8.1
怎么都 \(8\) 月了。。
\(90+100+40+0\) ,\(\text{T4}\) 本来有个输出大样例的 \(20\) 分,但被波波制裁取消了、、
T1 集合(mex)
题意:从 \(0 \sim n-1\) 的集合中随机抽出 \(k\) 个数,求这 \(k\) 个数的期望 \(\text{mex}\),答案对 \(998244353\) 取模。
\(\text{T1}\) 做的最快的一集。对于不考虑元素顺序的方案,可以使元素单调不降:这就是之前题的经验(如 \(7.29\) 的 "合并r")。考虑贡献,一个数 \(i\) 有贡献当且仅当 \(\forall j \in [0,i-1]\) 都在序列中,且 \(i\) 不在。那么贡献概率就可知了,便可以这样计算答案期望。
\(i\) 有贡献说明前 \(i\) 位必须是 \(0 \sim i-1\),唯一。所以我们只需考虑后 \(k-i\) 位的选法即可。
还有细节,\(i\) 能不能取 \(k\)?想想 \(i=k\) 有贡献即前 \(k\) 位放满 \(0 \sim k-1\),没有位置放 \(k\) 了,但此时的 \(\text{mex}\) 恰为 \(k\),情况数为 \(1\)。
为什么 \(90\) 分?考虑 \(n=k\) 的情况,那就是所有数放满,方案唯一,答案期望为 \(n+1\)。但是按上面的式子,\(C_{n-i-1}^{k-i} = C_{n-i-1}^{n-i}=0\),就错了。
T2 取模
题意:给出 \(n,m\),对于长为 \(n\) 的序列 \(a\),有 \(\forall a_i \in [1,m]\),求所有的
\[\sum_{i=1}^{n-1}\sum_{j=i+1}^n\lfloor \frac{a_i+a_j}{2} \rfloor \]答案对 \(998244353\) 取模,\(n,m\le10^6\)。
前俩都是数学题,这个可以按照前天的 \(\text{T3}\) 大力拆式子,也可以考虑累计贡献,还可以按照奇偶性算。
第一种做法:累计贡献
考虑两个位置 \((a,b)\),设其贡献为 \(x_{a,b}\),剩下 \(n-2\) 个位置随便放,显然有:
发现这样的二元组一共有 \(\frac{n(n-1)}{2}\) 个。
然后怎么搞呢,枚举 \(i,j\) 就 \(O(m^2)\) 了,不好。
有一个很神仙的思路,直接枚举 \(i+j\),再算 \(i\) 和 \(j\) 的可能情况。\(i+j \in [2,2m]\),复杂度 \(O(m)\)。
现在我们要解决的问题就是从 \(i+j\) 的和反推 \(i,j\) 种类,其实不太好搞。可以想到的是
- 当 \(i+j \in [2,m+1]\) 时,\(i\) 和 \(j\) 可以取到 \([1,m]\) 中的任意值,情况数是 \(i+j-1\)。
- 而当 \(i+j \in [m+1,2m]\) 时,并不是随便两个 \(i,j\) 就能凑出来这样的 \(i+j\) 了,怎么计算?考虑找到上界:令 \(a=m\),\(b=i+j-m\);下界:\(a=i+j-m\),\(b=m\),这中间有 \(|(i+j-m)-(m)|+1 = 2m-(i+j)+1\) 种,那么这就是情况数了。
然后就可以 \(O(m)\) 求了。
但是机房巨佬把它化简成了 \(O(\log n)\)……
第二种做法:大力爆拆
没啥好说的。
求:
记答案为 \(f_n\),拆递推式。
中间那项不好搞,设:
\(g(0) = 0\),那 \(f(2)= g(1) = \displaystyle\sum_{i=1}^m \sum_{j=1}^m \lfloor\frac{i+j}2\rfloor\),这玩意 \(O(m)\) 预处理扫一遍累答案,后面 \(O(n)\) 递推。
第三种做法:奇偶性
记 \(ans1 = \displaystyle\sum_{i=1}^{n-1} \sum_{j=i+1}^{n}a_i+a_j\),就可以算出来 \(\frac{ans1}{2}\) 记为初始答案,但是有偏差。因为每当奇偶性相同的两数放在一起,最终对答案是无额外影响的:因为是偶数,即不会被下取整砍掉一些答案。但当奇偶性不同的 \(a_i,a_j\) 加起来时,和就会多一个奇数。而每两个奇数就会比单独向下取整多贡献一个 \(1\),那答案就会多 \(1\)。记 \(ans2\) 为奇偶性不同的 \(a_i,a_j\) 对个数,所以最终答案是 \(ans = \frac{ans1}{2}-\frac{ans2}{2}=\frac{ans1-ans2}{2}\)。
- \(ans1\) 的计算
序列一共有 \(m^n\) 种形态,每一个序列有 \(n\) 个位置,每一个位置上的数会被计算 \(n-1\) 次贡献。相当于所有 \([1,m]\) 的数一共会被算 \(m^n\cdot n(n-1)\) 次,每个数等价,那么每个数都被计算 \(\frac{m^n\cdot n(n-1)}{m}\) 次,考虑答案 \(\sum_{i=1}^{m} \frac{m^n\cdot n(n-1)}{m}\),其实就是 \(\frac{m(m+1)}{2}\frac{m^n\cdot n(n-1)}{m}\) 即
- \(ans2\) 的计算
一共 \(n\) 个位置,枚举放偶数的位置个数为 \(i\),放奇数的位置就有 \(n-i\) 个,就有\(i(n-i)\) 个对。这 \(i\) 个位置 \(C_{n}^{i}\) 种放法,设 \(cnt1\) 为 \([1,m]\) 里奇数个数,\(cnt2\) 为偶数。每一个偶数位置都有 \(cnt2\) 个数能放,\({cnt2}^{i}\),奇数位置同理。
T3 魔法
考虑用 \(vis_{i,j}\) 表示点 \(i\) 能否到达点 \(j\)。由于空间限制,使用 bitset
。预处理时从点 \(i\) 暴力向两边去扫直到遇到比 \(a_i\) 大的点。
可以发现:
- 任意两个魔力值相同的点所能到达的点是相同的
- 如果一个点能到达一个点 \(i\),那么与 \(a_i\) 值相同的点它都能到达。
于是我们按照魔力值分类,统计每个值能到达哪些点。
我们在扫的时候也能进行优化。我们扫到魔力值相同的点时直接 break
(因为那个值在遍历时会继续往后扫);每扫到一个比它小的点就累加上;扫到刚好比它小 \(1\) 的点,累加后直接 break(比它小 \(1\) 的点必然会包含所有比它小的点的信息,该走的都走过了)。
这个复杂度上界是 \((\frac{n^2}{w})\),但跑的很快。
当然还有 \(\text{XiaoLe\_MC3}\) 的线段树优化简图+ Tarjan
缩点 + DAG_dfs
的做法,复杂度瓶颈也是 bitset
。
T4 排位
考虑转移降星保护卡的数量。易得
\(ans=(B+1+num_{降星保护卡})-(n-B)\)
设\(dp[i][j][k]\)表示前\(i\)局,胜了\(j\)局,当前局为胜(\(k=1\))或败(\(k=0\))
- 对于\(0\)操作,有
- 对于\(1\)操作,有
- 对当前操作,如果前\(d\)局连胜,那么
- 对于\(?\)操作,只需将上方\(0\)与\(1\)的情况全部转移即可
连胜情况可以直接预处理
最后考虑一点小特判
- 如果降星保护卡数大于败局数,那么只能使用较小者的数量 (其实如果你不判也能AC)
- 如果最后一局胜利且\(ans\)刚好是\(m\)的倍数,那么需要升段
- 若\(ans \le 0\),那么需要输出1(题目要求)
时间复杂度\(O(nB)\)
cin>>d>>B;
cin>>s;
n=s.length();
memset(dp,-0x3f,sizeof(dp));
dp[0][0][0]=dp[0][0][1]=0;
int ls=0;
for(int i=1;i<=n;i++){
if(s[i-1]=='0') ls=0;
else{
ls++;
if(ls>=d) combo[i]=true;
}
}
for(int i=1;i<=n;i++){
int minn=min(i,B);
for(int j=0;j<=minn;j++){
if(s[i-1]=='0'){
dp[i][j][0]=max({dp[i][j][0],dp[i-1][j][0],dp[i-1][j][1]});
}
else if(s[i-1]=='1'){
if(j>=1) dp[i][j][1]=max({dp[i][j][1],dp[i-1][j-1][0],dp[i-1][j-1][1]});
if(j>=d&&i>=d&&combo[i]) dp[i][j][1]=max({dp[i][j][1],dp[i-d][j-d][0]+1,dp[i-d][j-d][1]+1});
}
else /*if(s[i-1]=='?')*/ {
dp[i][j][0]=max({dp[i][j][0],dp[i-1][j][0],dp[i-1][j][1]});
if(j>=1) dp[i][j][1]=max({dp[i][j][1],dp[i-1][j-1][0],dp[i-1][j-1][1]});
if(j>=d&&i>=d&&combo[i]) dp[i][j][1]=max({dp[i][j][1],dp[i-d][j-d][0]+1,dp[i-d][j-d][1]+1});
}
}
}
int maxn=max(dp[n][B][0],dp[n][B][1]);
maxn=min(maxn,n-B);
int ans=B*2-n+maxn+1;
if(ans<=0){cout<<1;return 0;}
if(dp[n][B][0]<dp[n][B][1]&&ans%m==0)ans=ans/m;
else ans=ans/m+1;
cout<<ans<<"\n";
return 0;