2019-2020 10th BSUIR Open Programming Championship. Semifinal 题解
数学签到题比较多。gym103637
Tag
A(概率期望,计数DP)
B(字符串索引)
C(分类讨论)
F(概率期望,NTT卷积)
I(找规律,数论)
J(递推数列,矩阵快速幂)
K(暴力,贪心)
L(模拟,数论)
A. Agile permutation
称第一种操作为 swap(每次代价为a),第二种操作为 shuffle(每次代价为b)
考虑只用 swap 操作的子问题,对于任给定的排列,至少要 swap 几次?
- 答案是: $n - $ 轮换环个数。
轮换环个数是啥
不理解的手动画图理解下
对于一个给定排列 \(p=(p_1,p_2,...,p_n)\),考虑一个结点编号为 \(1..n\) 的有向图,对 \(i=1..n\),有一条 \(i\) 指向 \(p_i\) 的有向边。
由于每个点入度出度均为 \(1\),共 \(n\) 个点 \(n\) 条边,该有向图实际上是若干个不相交的简单环的并,这些环的数量就称为此排列的轮换环数。
感性证明:
末状态:排列已经升序,此时恰有 n 个一元环(每个点自己指向自己),需要 0 次操作。
(动手模拟下 swap \(p_i\),\(p_j\) 对有向图的影响)如果i,j这两个点原先在同一个环上,那这个环就被切开,成了两个长度之和等于原环的更小的非空环;如果这两个点原先不在一个环上,这两个环就会合并成为一个长度等于原先两环之和的更大的环。
换句话说,每次 swap 可以将任一个长度>1的环随便切一刀变成两半,或者将任意两个小环合并成更大的环,而目标是将环切成 n 个,贪心地一直切,最小 swap 次数就是 n - 初状态环数
可 \(O(n)\) 计算出只 swap 初始排列所需要的最小代价。
加入 shuffle 操作后,该怎么安排这两种操作?
由于 shuffle 是均匀随机地打乱这个序列,若进行了若干次 swap 后再 shuffle 一次,那前功尽弃,则最优的策略必是先进行若干(可以为0)次shuffle,再进行若干次(可以为0)swap,直到该排列达到目标状态。
如果当前的排列足够合适(环数比较多,即需要的 swap 次数比较少),那直接开始 swap 到结束;否则就再 shuffle 一次。
可以猜测该式成立:
\(E(第一次shuffle后,还需要的代价)=P(环数够小)*E(环数够小的前提下,直接swap的最小操作数)*a+(1-P(环数够小))*(b+E(第二次shuffle后,还需要的代价))\)
再细想,我们希望的期望应该是需要收敛到一个min值的,不论已经 shuffle 了几次,每一次策略选择存在一个固定不变的“分界”,对于期望的计算也应该与已经进行了几次 shuffle 无关,考虑枚举这个“分界”:
存在整数 \(t\in [1,n]\),若当前环数 \(\geq t\),则直接 swap,否则继续 shuffle。对每一个 \(t\) 分别计算该策略的总代价的期望,取其中的最小值。
具体地,对于固定的一个 \(t\),
设 \(F\) 为对于随机的排列,需要的代价期望;
设 \(P\) 为对于随机的排列,环数>=t 的概率;
设 \(G\) 为若当前排列环数>=t,需要的 swap 次数的期望,列式:
- \(F=PGa+(1-P)(b+F)\)
- 即 \(F=Ga+\frac{1-P}{P}b\)
最后答案为 \(Min(F+b,直接swap原排列的代价)\)
其中 \(P\) 和 \(G\) 的计算需要预处理有多少个 \(n\) 元排列恰有 \(m\) 个轮换环,记为 \(num[n][m]\),实际上这是第一类斯特林数,不过不熟悉的话也不难写出 \(O(n^2)\) 的DP:
类似于经典问题“带标号连通图计数”,要计算 \(num[n][m]\),考虑 \(n\) 号点和哪些点联通,
-
\(n\) 号点成自环,这种情况方案数是 \(num[n-1][m-1]\);
-
\(n\) 号点不成自环,那就是在“n-1个点,m个环”的图中选一个位置插入,有n-1个位置可以插,这种情况方案数是 \((n-1)DP[n-1][m]\)
边界条件:\(DP[n][0]=[n==0]\)
由于 n 元排列总数最多有 \(20!\) 个,这样算不会爆long long。
一点小问题
基于枚举 \(t\) 取然后取最值的做法考虑用浮点数来比较大小,再精确计算模意义下答案,是否可以加强到 \(n\leq 50\) 或者更大?
若能根据 \(n,a,b\) 直接计算合适的 \(t\),输出模NTT质数的答案,是否可以加强到 \(n\leq 10^5\) 的数量级?
B. BSUIR Open X
对 "BSUIROPENX",检查下它的每种前后缀在输入的那些字符串中出现了多少次,根据乘法原理和加法原理统计下答案。数据范围很小,用 map<string,int> 来索引下就行。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const string str = "BSUIROPENX";
int n;
map<string, int> mp;
signed main() {
ios::sync_with_stdio(0);cin.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i) {
string si;
cin >> si;
++mp[si];
}
LL ans = 0;
for (int i = 1; i < str.size(); ++i) {
int u = mp[string(str.begin(), str.begin() + i)];
int v = mp[string(str.begin() + i, str.end())];
ans += (LL)u * v;
}
cout << ans << '\n';
return 0;
}
F. Function analysis
题意有点绕。。。翻译成人话就是:
从 \(1...n\) 的整数中有放回地等概率随机抽取 \(m\) 次,问:抽到的 \(m\) 个数排升序后,第 \(d\) 个数大于 \(k\) 的概率?
给定 \(n,d,k\),对 \(m=d...n\) 输出答案,模 \(998244353\)
首先转化下题意:
第 \(d\) 个数大于 \(k\) \(\iff\) 不超过 \(k\) 的数的数量不超过 \(d-1\) 个
于是就变成了高中数学概率题。。。由熟知的二项式与概率,
根据 \(P(一次抽到的数不超过k)=\frac{k}{n}\)
则 \(Ans(m)=\sum_{i=0}^{d-1}C_m^iP^i(1-P)^{m-i}\)
变形为 \(m!\sum_{i=0}^{d-1}\frac{P^i}{i!}\frac{(1-P)^{m-i}}{(m-i)!}\)
计算 \(A_n=[n<d]\frac{P^n}{n!}\) 和 \(B_n=\frac{(1-P)^{n}}{n!}\) 的卷积就行。
I. Items in boxes
据乘法原理,答案是 \(a^{2^n}(mod\ 2^{n+2})\)
赛时思路
首先特判掉 \(n=1\) 的情形,得到条件 \(n\geq 2\)
然后看下 \(a\) 是不是偶数,因此时 \(n\geq 2\),\(a\) 是偶数的话直接输出 \(0\)
是奇数的话,这时样例是输出 \(1\) 的,想了下模 \(2^{n+2}\) 这么大的数,余数太大的话甚至输出就T了,盲猜余数只能是 \(1\),就A了
只证明最后一种情形
(归纳)对于 \(a\) 为奇数,\(n\geq 1\),要证 \(a^{2^n}\equiv 1(mod\ 2^{n+2})\)
(1)对于 \(n=1\),设 \(a=2k+1\),\(k\in \Z\),
则 \(a^2=4k^2+4k+1=4k(k+1)+1\)
由于 \(k\) 和 \(k+1\) 必为一奇一偶,则 \(4k(k+1)\) 是 \(8\) 的倍数,
即 \(a^{2^1}\equiv 1(mod\ 2^{1+2})\)
(2)假设 \(n\) 结论成立,即 \(\exists k\in \Z\) 使得 \(a^{2^n}=2^{n+2}k+1\)
则 \(a^{2^{n+1}}=(a^{2^n})^2=2^{2n+4}k^2+2^{n+3}k+1\),
由于 \(2^{n+1+2}|(2^{2n+4}k^2+2^{n+3}k)\),
则 \(a^{2^{n+1}}\equiv 1(mod\ 2^{{n+1}+2})\) 成立 (证毕)
J. Jenga(队友做的 stO zzwtx)
- 特判 n=1
- 关于 \(n\) 的递推数列要推对(我不会QAQ)
- 相互依赖的几个线性递推数列也可以矩阵快速幂,矩阵多开几行就好了
K. K-ones xor
先考虑一个简化版问题
构造一个x,使得 \(\sum_{i=1}^n(b_i\ xor\ x)\) 最大,要求 x 的二进制表示中 1 的个数不超过给定的k,若有多组解输出 x 最小的解
比较套路的按位讨论贪心题。可行的复杂度 \(O(wn+w\log w)\),其中 w 是数的位数。
回到原题,但是不好像上面那样做,只看 \(x\) 的其中一个数位的话,难以确定它对于特定的 \(a[i]\) 到底有无贡献,于是再观察下 $ ai=max(ai,ai⊕x) $ 这个条件——怎样的 \(x\) 会使得 \(a_i\ xor\ x>a_i\) 呢?
还是按二进制位去考虑(不妨设 \(x>0\) )。因为整数的大小比较是从二进制高位开始到低位,我们也从高位到低位观察下每一位的 \(x\) 和 \(a_i\):
从最高位开始 \(x\) 如果有一段 \(0\)(当然也可能没有),这些位置上 \(a_i\) 的值不变,这几位对答案没有影响;
则不妨考虑 \(x\) 的最高位的 1:
- 如果 \(a_i\) 在这一位上是 \(1\),那 xor 完后会变成 \(0\),
考虑到这个等比数列:1,2,4,8...,它任意位置的前缀部分和一定严格小于下一个位置的数,\(1+2+...+2^{N-1}<2^N\)
这说明,如果 \(a_i\) 在这个位置上亏了一个 \(1\),那在更低的数位上补回来再多的 \(1\) 都还是亏的,所以这个 \(1\) 是绝对不能亏的,换句话说,此时已经可以确定了(不管它们更低位长啥样),\(a_i\) 不能 xor \(x\)
- 如果 \(a_i\) 在这一位上是 \(0\),那 xor 完后会变成 \(1\),
同理,如果在这一位赚了个 \(1\),在更低的数位上不管怎么亏,总体都是赚的,换句话说,此时已经可以确定必须让 \(a_i\) xor \(x\)
即:如果 \(x\) 的最高的 \(1\) 的位置确定了,那 \(a[1..n]\) 中哪些数要和 \(x\) 异或,哪些不和 \(x\) 异或也都确定了
于是先枚举 \(x\) 的最高位的 \(1\),假设是第 \(j\) 位,对应的数值为 \(2^j\),\(0\leq j<m\),
将此时参与异或的那些 a[] 单独拿出,就是在解决开头所说的简化版问题:计算数位 \(2^i,0\leq i<j\) 对它们的贡献,取其中 \(k-1\) 个贡献最大(贡献相同时,数位低的优先级更高)且为贡献 \(>0\) 的数位,就是这个 \(j\) 的答案
时间复杂度 \(O(m^2 n)\),实现需要注意细节

浙公网安备 33010602011771号