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)\),实现需要注意细节

posted @ 2022-05-01 18:37  ilxT  阅读(492)  评论(0)    收藏  举报