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=(p1,p2,...,pn),考虑一个结点编号为 1..n 的有向图,对 i=1..n,有一条 i 指向 pi 的有向边。
由于每个点入度出度均为 1,共 n 个点 n 条边,该有向图实际上是若干个不相交的简单环的并,这些环的数量就称为此排列的轮换环数。

感性证明:
末状态:排列已经升序,此时恰有 n 个一元环(每个点自己指向自己),需要 0 次操作。
(动手模拟下 swap pi,pj 对有向图的影响)如果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+(1P())(b+E(shuffle))

再细想,我们希望的期望应该是需要收敛到一个min值的,不论已经 shuffle 了几次,每一次策略选择存在一个固定不变的“分界”,对于期望的计算也应该与已经进行了几次 shuffle 无关,考虑枚举这个“分界”:

存在整数 t[1,n],若当前环数 t,则直接 swap,否则继续 shuffle。对每一个 t 分别计算该策略的总代价的期望,取其中的最小值。
具体地,对于固定的一个 t
F 为对于随机的排列,需要的代价期望;
P 为对于随机的排列,环数>=t 的概率;
G 为若当前排列环数>=t,需要的 swap 次数的期望,列式:

  • F=PGa+(1P)(b+F)
  • F=Ga+1PPb

最后答案为 Min(F+b,swap)

其中 PG 的计算需要预处理有多少个 n 元排列恰有 m 个轮换环,记为 num[n][m],实际上这是第一类斯特林数,不过不熟悉的话也不难写出 O(n2) 的DP:

类似于经典问题“带标号连通图计数”,要计算 num[n][m],考虑 n 号点和哪些点联通,

  • n 号点成自环,这种情况方案数是 num[n1][m1]

  • n 号点不成自环,那就是在“n-1个点,m个环”的图中选一个位置插入,有n-1个位置可以插,这种情况方案数是 (n1)DP[n1][m]

边界条件:DP[n][0]=[n==0]

由于 n 元排列总数最多有 20! 个,这样算不会爆long long。

一点小问题

基于枚举 t 取然后取最值的做法考虑用浮点数来比较大小,再精确计算模意义下答案,是否可以加强到 n50 或者更大?
若能根据 n,a,b 直接计算合适的 t,输出模NTT质数的答案,是否可以加强到 n105 的数量级?

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 不超过 k 的数的数量不超过 d1

于是就变成了高中数学概率题。。。由熟知的二项式与概率,

根据 P(k)=kn

Ans(m)=i=0d1CmiPi(1P)mi

变形为 m!i=0d1Pii!(1P)mi(mi)!

计算 An=[n<d]Pnn!Bn=(1P)nn! 的卷积就行。

I. Items in boxes

据乘法原理,答案是 a2n(mod 2n+2)

赛时思路
首先特判掉 n=1 的情形,得到条件 n2
然后看下 a 是不是偶数,因此时 n2a 是偶数的话直接输出 0
是奇数的话,这时样例是输出 1 的,想了下模 2n+2 这么大的数,余数太大的话甚至输出就T了,盲猜余数只能是 1,就A了

只证明最后一种情形

(归纳)对于 a 为奇数,n1,要证 a2n1(mod 2n+2)

(1)对于 n=1,设 a=2k+1kZ

a2=4k2+4k+1=4k(k+1)+1

由于 kk+1 必为一奇一偶,则 4k(k+1)8 的倍数,

a211(mod 21+2)

(2)假设 n 结论成立,即 kZ 使得 a2n=2n+2k+1

a2n+1=(a2n)2=22n+4k2+2n+3k+1

由于 2n+1+2|(22n+4k2+2n+3k)

a2n+11(mod 2n+1+2) 成立 (证毕)

J. Jenga(队友做的 stO zzwtx)

  • 特判 n=1
  • 关于 n 的递推数列要推对(我不会QAQ)
  • 相互依赖的几个线性递推数列也可以矩阵快速幂,矩阵多开几行就好了

K. K-ones xor

先考虑一个简化版问题

构造一个x,使得 i=1n(bi xor x) 最大,要求 x 的二进制表示中 1 的个数不超过给定的k,若有多组解输出 x 最小的解

比较套路的按位讨论贪心题。可行的复杂度 O(wn+wlogw),其中 w 是数的位数。

回到原题,但是不好像上面那样做,只看 x 的其中一个数位的话,难以确定它对于特定的 a[i] 到底有无贡献,于是再观察下 ai=max(ai,aix) 这个条件——怎样的 x 会使得 ai xor x>ai 呢?

还是按二进制位去考虑(不妨设 x>0 )。因为整数的大小比较是从二进制高位开始到低位,我们也从高位到低位观察下每一位的 xai:

从最高位开始 x 如果有一段 0(当然也可能没有),这些位置上 ai 的值不变,这几位对答案没有影响;

则不妨考虑 x 的最高位的 1:

  • 如果 ai 在这一位上是 1,那 xor 完后会变成 0

考虑到这个等比数列:1,2,4,8...,它任意位置的前缀部分和一定严格小于下一个位置的数,1+2+...+2N1<2N

这说明,如果 ai 在这个位置上亏了一个 1,那在更低的数位上补回来再多的 1 都还是亏的,所以这个 1 是绝对不能亏的,换句话说,此时已经可以确定了(不管它们更低位长啥样),ai 不能 xor x

  • 如果 ai 在这一位上是 0,那 xor 完后会变成 1

同理,如果在这一位赚了个 1,在更低的数位上不管怎么亏,总体都是赚的,换句话说,此时已经可以确定必须让 ai xor x

即:如果 x 的最高的 1 的位置确定了,那 a[1..n] 中哪些数要和 x 异或,哪些不和 x 异或也都确定了

于是先枚举 x 的最高位的 1,假设是第 j 位,对应的数值为 2j0j<m
将此时参与异或的那些 a[] 单独拿出,就是在解决开头所说的简化版问题:计算数位 2i,0i<j 对它们的贡献,取其中 k1 个贡献最大(贡献相同时,数位低的优先级更高)且为贡献 >0 的数位,就是这个 j 的答案

时间复杂度 O(m2n),实现需要注意细节

posted @   ilxT  阅读(444)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示

目录导航