主线任务:上 2100,支线任务:成为一名优秀的代码攻击者。
最近感觉自己又行了。就很想重新开始打 Codeforces ,这里罗列一下从 2020/05/16 开始提交的(不是特别水的)AC 代码。想有生之年上一次黄,先定个小目标上个 1900(最高 1854,当前 1829,换号了!新账号于 2020/11/18 达成 1900+ 成就,紫名:)也太好看了吧)菜鸡 dna049 加油 下班了!
不再打除了 codeforces, atcoder, 洛谷之外的日常比赛。有空整理一下比赛,好的题目或比赛记得 star。优雅的代码极大的避免了低级错误 ,边界处理至少占三分之一的工作量!
不要急着写代码,不要急着写代码,不要急着写代码,重要的事情说三遍!!! 优先做 Codeforces, Atcoder, 其它平台的题目可以在 vjudge 中提交(洛谷和 UOJ 除外),POJ 和 HDU 不在搞了。 不要为了补题,Rating,浪费太多的时间 把一个问题写成简洁清晰的代码,才算是真的理解了问题!代码能力是快速综合规划的能力。 至少先上 2100,再去写介绍性博客,分低会影响博客的质量!至少上 2100 才配出题 不再补比赛中没有考虑过的题
准备工作 珍惜每一场比赛,不要怕掉分(怕的原因就是不自信),提前 8 到 10 分钟 ,准备以下事情(加号表示有用次数,2021 年开始计算):
如果没注册,赶紧注册。身体状态不好时,用小号打,上分不易。+2
闹钟(每个题不要长期思考,读完题没明显思路直接跳!只要不被卡题,我上 2100 应该不难,所以闹钟很重要) +2
纸和笔(ipad + pencil + 闹钟 亦可)+0(表示:永远有用)
SageMath,用于一些数据测试
打开自己的网站用于模板
打开 VScode,查看 CF 比赛号,开始 cf race CF 比赛号
+0
认真读题,特别是样例特别少的时候!
计算式要列的清晰,并且最好是好输入的(think twice, code once)。+3
不要膨胀,老老实实先做简单题,没思路就直接跳过(拒绝拖延,但是一定记得要回来把简单题补了!) +1
有思路赶紧写(但是思路清晰再写!无论问题难与否,大家都是一样的,打好自己!),不要觉得时间很多
最后 15 分钟(或者觉得没题能做),如果没有思路就不要再做题了,攻击代码去。开 room hack!锁题前检查边界,不要最后 RE 了(根据做题时,可能的 hack 点来找 hack,可以现在 EDU 场练习如何 hack。比赛结束看看自己 room 有那些没有过最终测试的。通用 hack 点:答案超了 long long,用 ceil 向上取整。注意有些狗喜欢 #define int long long
)
前面题目被卡会特别影响做后面题的心态!
注意到题目中简单的性质结论都应该写下来,拼一下可能就知道怎么做了。注意特殊问题的特殊解法。
WA 之后,很可能方法错了,或者代码有问题,没有本质修改的话,不要乱提交!
有子问题的题目,两个都会做,先交分数小的。
不要写这样的代码 :if(a[j++]), while(a[j++])
这种数列下标自加,特别容易出问题还不好查,特别在判断语句中。
$N=10^{18}$ 一般会有公式, $N = 2 \cdot 10^5$ 一般会用到排序、树状数组,线段树,等一系列 $O(n \log n)$ 算法,$N=1000$ 一般会用到 $dp$。模 998244353
可能会用到 NTT。
Codeforces 比赛规则:每题有基础分数,每题的分数根据时间线性递减到一半(所以有些大佬先做 CD,再做 AB),在最后一次提交前每次提交减 50 分,然后按照分数排名,有 hack,但是只有你过了初例并且 锁定题目 才能 hack。 Eductional 比赛规则:与 ICPC 一致,不过一次非 AC 提交罚时为 10 分钟(ICPC 20 分钟),难度比 Div2 稍难,需要更细心。(+10-184+69-184-20-21-77-36+15-11)最近不再打正式 Edu 场(2021-4-25) +++。
Codeforces Gobal Round:目前实力不支持做这个,容易掉分! 正常发挥都是能加分的!
查看 Codeforces 上的过题数 ,查看比赛 rating 折线 ,开始使用 cf-tool + WSL 打比赛(真香)。
题集 A 题题目理解错误被卡了一下,怕掉分就没敢做,其实只要做出 D 就可以加分,我当时不自信
A:注意到最多 n 次就开始稳定了 B:只考虑两个的情况就可以了 C:直接从头开始,然后找可能即可
我一开始以为要从后面开始找,浪费了点时间
D:一看这题就要用 mask,但是没想到是概率 + mask 看 jiangly 代码还学了一手卡时间技巧
这个 mak 的处理特别细节 $p \cdots 2^p$,值得学习,当然也可以直接暴力 $3^p$。
E:组合概率问题 有连续 n 个灯,如果一个长度为 k 的区间包含大于两个亮的灯就停止工作,问停止工作时亮灯的个数的期望
所以假设现在有 p 个亮灯但是没有停止工作,这等价于
所以种类是 $\binom{n - (p - 1)(k - 1)}{p}$
虽然这不是最终停止工作时的答案,但是它贡献了答案 。
官方题解 和我的提交
我一开始一直 TLE 原来是因为思路有问题,注意代码中求 sumPhi 会有很多公共项
注意到 $x = \min(\lfloor \frac{n}{i} \rfloor, \lfloor \frac{m}{j} \rfloor), gcd(i, j) = 1$ 等价于 $\gcd(xi, xj) = x$ 的最大的 $x$
这么说来这也是给出了更快做法(前提 $f(0) = 0$)
A:排序交错取值即可 B:你会发现只有 11 和 111 是有用的 C:这题我应该 C1, C2 一起做的 首先我们肯定会取所有非负的,然后尽量取最多,如果不能取,那么我们把最小的负数换掉即可。
用 优先队列比用 multiset 快
D:这题如果注意到最优策略必然是 AAAABBBCCCDDD 的样子,那么直接全排列处理一下即可 其实我一看到这题就知道要全排列,但是!!!我思考的中途忘了!
用 树状数组求逆序数或者直接暴力求都可以。
E:组合计数 首先,我们看计数非 0 的最小 k 为多少。考虑两个相邻(按照大小排序后)的后缀 $ax$ 和 $by$,若 $x > y$ 那么 $a < b$,否则 $a \leq b$。所以我们要看有多少种 $a \leq b$ 的可能,这样就可以知道最小需要多少个 k。假设有 e 个 相等。那么最小的 k 为 $n - e$。现在问题怎么计数,其实它等价于求 $a_1 + \cdots + a_n \leq k - (n - e)$ 的非负整数解的个数,答案显然是 $\binom{k + e}{n}$(先补成等于号,再插空法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;class BlockMinus { std ::vector <int > fa, sz, a; int l, delta, mx; int find (int x) { return x == fa[x] ? x : fa[x] = find (fa[x]); } void merge (int x, int y) { x = find (x); y = find (y); if (x == y) return ; fa[x] = y; sz[y] += sz[x]; sz[x] = 0 ; } void modifyPart (int ql, int qr, int x) { for (int i = ql; i < qr; ++i) { a[i] = find (a[i]); if (a[i] - delta > x) { --sz[a[i]]; a[i] = find (a[i] - x); ++sz[a[i]]; } } } void modifyAll (int x) { if (x < mx - delta - x) { for (int i = delta + 1 ; i <= x + delta; ++i) merge(i, i + x); delta += x; } else { for (int i = mx; i > x + delta; --i) merge(i, i - x); mx = x + delta; } } int queryPart (int ql, int qr, int x) { int ans = 0 ; for (int i = ql; i < qr; ++i) { if (find (a[i]) - delta == x) ++ans; } return ans; } int queryAll (int x) { x += delta; if (x > mx || find (x) != x) return 0 ; return sz[x]; } public : void init (const std ::vector <int > &_a, int _l, int _r) { l = _l, delta = 0 ; a = {_a.begin () + _l, _a.begin () + _r}; mx = *std ::max_element(a.begin (), a.end ()); fa.resize(mx + 1 ); std ::iota(fa.begin (), fa.end (), 0 ); sz.resize(mx + 1 ); for (auto x : a) ++sz[x]; } void modify (int ql, int qr, int x) { if (x >= mx - delta) return ; if (qr - ql == (int )a.size ()) modifyAll(x); else modifyPart(ql - l, qr - l, x); } int query (int ql, int qr, int x) { if (qr - ql == (int )a.size ()) return queryAll(x); return queryPart(ql - l, qr - l, x); } }; int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n, m; std ::cin >> n >> m; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; int sn = sqrt (n); std ::vector <BlockMinus> block; for (int i = 0 ; i < n; i += sn) { BlockMinus tmp; tmp.init(a, i, std ::min (i + sn, n)); block.emplace_back(tmp); } while (m--) { int op, l, r, val; std ::cin >> op >> l >> r >> val; --l; int il = l / sn, ir = r / sn; if (op == 1 ) { if (il == ir) { block[il].modify(l, r, val); } else { block[il].modify(l, il * sn + sn, val); for (int i = il + 1 ; i < ir; ++i) { block[i].modify(i * sn, i * sn + sn, val); } if (ir < block.size ()) block[ir].modify(ir * sn, r, val); } } else { int ans = 0 ; if (il == ir) { ans += block[il].query(l, r, val); } else { ans += block[il].query(l, il * sn + sn, val); for (int i = il + 1 ; i < ir; ++i) { ans += block[i].query(i * sn, i * sn + sn, val); } if (ir < block.size ()) ans += block[ir].query(ir * sn, r, val); } std ::cout << ans << '\n' ; } } return 0 ; }
A:典型线段树 但是 and 线段树,不知道怎么处理,后来看别人才知道,每个点最多 and 有效 bit 次。
D:区间 DP 这题数据太水了,$O(n^3)$ 裸就过了
F:预处理最小素因子直接算即可 这题可以类似求 $\pi(x)$ 做到 $O(n^{\frac{2}{3}})$ 从而计算到 $O(n^12)$ 但是此时要用 int128 存结果可能会很慢,但是 $O(n^10)$ 问题不大。
H:假路径压缩 这题把方向搞反了 WA 了几次
I:能难倒我的 SG 游戏题 这不就是经典阶梯 SG 吗?我在干什么!这种问题之前还遇到过了!
J:纸老虎 注意到题目的限制就说明了最多仅有一个没有匹配,正是那个全部被搞了的点。否则就全能匹配
K:签到水题 注意到只要总和大于等于 K 块,就总能只留下 $\lfloor \frac{n}{2} \rfloor$
A:异或纠错码 挺简单,但是还有有意义的一道题,反正就在最后面补 k 个 0,然后处理到最后,这 k 个字符就是答案。
D:不交区间维护
昨天 cf 722 div 1C 本质就是不交区间维护更新
很有意思的一题,注意到 C 必然是连续的一段自然数,我们先保存所有值的位置,然后我们从最小值开始遍历,用 Set 维护不交并的区间即可。
F:贪心 注意到 $b_i$ 是 $2$ 的幂次,因此我们可以让 $a$ 从小到大贪心的每次取最小,就不用担心大的因为先取了小的而没取到的问题。
J:用复数处理相似三角形 首先对一个图形进行伸缩和旋转是相似变换,那这不就相当于用一个复数乘以所有的端点吗?我们要最小的整格点,那么我们就除以它们的最大公约数即可,而 Euild 整环都可以用带余除法求最大公约数。注意这里我们做除法的时候要使得余数的模最小。
K:经过一轮取差取绝对值(经典分块) 根据和 Kelin 的 Talk,注意到值域是单调递减的。利用第二分块解决(为什么叫第二分块 ,有空可以挑战以下 Ynoi 分块)
最后根据 zimpha 的代码读懂了 第二分块 的做法。
然后取差之后绝对值也就相当于两次 第二分块 来持续处理一个区间。
注意到这题不像原始第二分块可以直接黑科技暴力过!
好高骛远导致又一次离 master 最近的一次
又是一次 dfs 序的应用!!!
A:绝对值最值问题 注意到最大值必然在边界取到,所以树上 DP 即可
B:找规律 DP 不难发现 $a_n = \sum_{i = 1}^{n - 1} a_i + d(n)$,所以整体复杂度 $O(n \log n)$
C:dp 序应用 这题我知道要用 dfs 序,我也知道 dfs 序的性质:$u$ 是 $v$ 的祖先,当且仅当 $in[u] \leq in[v] \leq out[v] \leq out[u]$,但是我们需要的是反过来,这样就很多了,所以我因为我做不了,然后就取读其它题,然后专注最后一题,未果。其实 dfs 还有一个基本的性质,不存在交叉的情况,即不存在 $u, v$ 满足 $in[u] \leq in[v], out[u] \leq out[v]$,所以如果它们不互为祖先关系,那么必然是两两不交的!
F:经典组合问题(Stirling 数和下降幂的应用) 题解一波对应操作,就把问题转化成求
注意到上述式子中 $j > k$ 时 $S(k, j) = 0$
A:可以直接 DP 设 a[n][k], b[n][k]
分别为 $n$ 与 $n - 1$ 不相连,相连的答案,则 $a[n][k] = (k + 1) a[n - 1][k + 1] + (n - 2 - k) a[n - 1][k] + k b[n - 1][k + 1] + (n - 1 - k) b[n - 1][k]$ $b[n][k] = b[n - 1][k - 1] + b[n - 1][k] + a[n - 1][k - 1] * 2$
B:构造排列使得 $|a_i - i|$ 还是一个排列 首先根据奇偶性发现 $n \mod 4 = 0, 1$ 才有可能。打表找规律 取 $n = 4, 5, 8, 9, 12$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;template <typename T>void debug (std ::vector <T> a) { for (auto &i : a) std ::cout << i << ' ' ; std ::cout << std ::endl ; } int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n; std ::cin >> n; std ::vector <int > a (n) ; std ::iota(a.begin (), a.end (), 0 ); do { std ::set <int > S; for (int i = 0 ; i < n; ++i) S.insert(abs (a[i] - i)); if (S.size () == n) debug(a); } while (std ::next_permutation(a.begin (), a.end ())); return 0 ; }
我们想前 k 个放 $n - 1, … n - k$, 后 k 个 放 $k + 1, \cdots, 1$, 然后把 0,放进去,再处理。
乘积图的最小生成树 乘积图的最小生成树也是贪心的做法:第一张图两个点相连,需要连第二张图剩下的点的个数,相当于此后第一张图的点就少了一个。那么利用 排序 + DSU 贪心即可。
I:思路简单却很坑的一题 首先解方程 $2x + y = a, 3x + y = b$ 显然解为 $x = b - a, y = a - 2x$。显然我们要求 $x \geq 0, y \geq 0$。然后考虑一下第一个人的特殊性:要求 $x >= n - 1$,最重要是:
平局需要至少两个人参与!
而就要求所有平局数为偶数,且平局数的最大值不能大于其它的和,并且只要这样就够了。这是因为我们每次取平局数最大的和平局数次大的打一场平局,那么此时依然满足这个性质!
还有就是,是路径而非右边相连,所以就需要赢的次数和大于 $n - 1$!还要特判 $n = 1, 2$ 的情况!
这题也太坑了,连题目作者都没有考虑平局有可能不行的情况!
J:简单素数问题 首先注意到 $(10 n + 5)^2 = 100 n(n + 1) + 25$,那么答案就是 $n$ 和 $n + 1$ 的互异素因子个数和。
L:找递推关系式 要特别细心才行!你会发现直接递推关系式长度与 n 有关。。所以我们要取多个递推关系来搞,分析下来有 8 种情况要考虑:{, , }, {, 111, 111}, {, 1, 11}, {1, , 11}, {, 01, 11}, {, , 111}, {, 11, 1}, {1, 11, }
,我们用 $a_i$ 表示 $a_i(n)$,$a_i’$ 表示 $a_i(n - 1)$,那么 $a_0 = a_0’ + a_1’ + a_2’ + a_3’ + a_4’$,(通过观察右上角的那个快怎么来的,其它的观察最长的那个怎么来的) $a_1 = a_2’ + a_5’ + a_6’$, $a_2 = a_0’ + a_7’$, $a_3 = a_2’$(这里用了一下对称), $a_4 = a_5 = a_0’$, $a_7 = a_8 = a_0’ + a_3’$(这里也应用了一次对称)。
因此这个问题可以优化成 6 种 case,以下是优化后的情况
使用 sageMath
1 2 3 4 5 6 7 8 9 10 A = matrix(6 ) A[:, 0 ] = A[0 , :] = A[1 , 2 ] = A[1 , 4 ] = A[1 , 5 ] = A[2 , 5 ] = A[3 , 2 ] = A[5 , 3 ] = 1 A[0 , 5 ] = A[1 , 0 ] = A[3 , 0 ] = 0 b = matrix(6 , 1 ) b[0 ] = 1 for i in range(6 ): print((A^i * b)[0 ]) A.characteristic_polynomial()
这个问题可以自动化吗?
A:签到题 筛子问题
B:签到题 数字字符倒过来
C: 水题 利用 map,注意到 C 数列本质可变成 B 数列。
D:组合数序 $a$ 个 ‘a’,$b$ 个 ‘b’,注意到,如果第一个位置填 ‘a’,那么最多有 $\binom{a + b - 1}{b}$ 个数,如果 k 大于它,那么说明只能填 ‘b’ 此时 k 要减去这个数。
E:经典树上问题 这个问题我一开始一直想用倍增做,然后发现复杂度不对,但是想不到别的做法,于是我就开始倍增做,然后代码直接爆炸,最后 TLE。
再次发现 dfs 序的重要性!!!
A:水题 利用 $a, b \geq 0$ 时 $a + b \geq a \oplus b$
C:map 利用 map,然后 map 前缀和,再用一个 vector 保存 map 的值,再二分查找即可
E:找一个长为 n 的排列,最长递增为长度为 x,最长递减为 y 不妨假设 $x > y$,那么我们构造 $n - x + 1, \cdots, n$ 就可以让 n 变成 $n - x$, y 减一了。
H:田忌赛马 排序加 set 即可
J:签到题 K:考虑前缀与后缀,然后“相加”即可 L:经典问题:特殊的图的可达性问题 通过原图和反图分别求 “可达点” 的个数(这里并不是真的求了可达点)。我们从图的入度为 0 的点开始考虑,如果考虑到 $u$ 发现当前队列中还有点,就说明这个点不能到达它除了它祖先外的所有点,因此它不可能成为答案,那就不计算它的可达数,即使计算了也不一定正确(或者说不会大于自己的可达点个数)。
这个题好特殊啊,保证了能求由很难拓展!
N:水题 分奇偶即可
A 求最大的 $k$ 使得 $n \And (n−1) \And (n−2) \And … \And k = 0$,显然二进制 n 为 $1…$,不把首个 1 消了不可能为 0,所以答案就是 1 << std::__lg(n) - 1
因为 cf test 太卡导致 cf submit 时 00::02:01 气死了。我去改 cf-tool 设置!
B B1 比较简单,一开始就是回文,此时如果 n 为偶数,后手必胜;若 n 为奇数,此时中心元素若是 1,那么就跟 n 为偶数没区别;否则中心元为 0, 那么先手必抢这一点,那么如果还有多余的 0,那么先手必赢,否则必输。
B2 如果一开始不是回文,那么先手想怎样就怎么样,等到只有一个地方不是回文的时候果断把那个位置一填,就基本赢了,唯一的可能和就是 n 为奇数中心元为 0,且只有两个 0
C 真的气,我以为是求逆序数,交了一把逆序数上去,吐了。后来发现是看相等的地方,那么直接 map 一下考虑贡献即可。
D 首先注意到 $Mex(S) > i$ 当且仅当 $S$ 中存在 $0, 1, 2, \cdots, i$。
不包含 $i$ 可以推出 $Mex(S) \leq i$ 但这不是充要的。
那么我们找路径中包含它们的值即可。因此我们可以让树的每个节点记录它的子孙中节点编号的最小值(并且把这个分支当作真儿子),且记录以它为子树的大小。
$Mex(S) = 0$ 当且仅当 $S$ 中不包含 $0$,那么就是 0 为根的树,每一个分支中任取两点再求和即可 $Mex(S) > 0$ 当且仅当去掉 $Mex(S) = 0$ 的情况 $Mex(S) > 1$ 当且仅当包含 1,那么答案就是 $sz[1] \cdot (n - sz[son[0]])$ 而非 $sz[1] \cdot (n - sz[1])$!因为 0 和 1 不一定直连啊!哎。 如果 i + 1 在 i 的子树中,那么就一直往下跑,答案为 $sz[i + 1] \cdot (n - sz[son[0])$,否则我们还有一次机会,这个时候 $i + 1$ 在另外一个分支上,在哪无所谓,答案变成了 $sz[i + 1] \cdot sz[i_0]$。再往下跑,不满足条件就结束。
最后 $|Mex(S) = i| = |Mex(S) > i - 1| - |Mex(S) > i|$
特别的生气!!! 我为什么弱智的把 $sz[1]$ 和 $sz[son[0]]$ 搞混淆了!!! 啊,好气啊!就差这么一点,这个题做出来直接能 +100 多,就能打破我的最佳排名记录了。后来我发现我想错的点不止一个,确实是自己实力不够。
A:NTT-friendly 数:枚举 b 即可 B:一开始以为是贪心 这个 评论 一下子顿悟了
C:这种问题之前遇到了,竟然一下子不知道怎么做 意识到区间可行当且仅当它们的交错和(alternating sum)为 0,就简单了。
注意交错和有 n + 1 个值。
A:送分诱导参赛上当 B:要考虑到头为 n 尾部为 1 的情况 C:感觉挺麻烦就去做 D 去了 首先注意到每次奇数都会变成偶数,偶数变成奇数,就分两种情况处理即可。然后排序,一左一右的配对,用两个堆保存当前情况。最后堆非空只会出现 L..LR..R
的情况,然后相邻同样的两类配对,反射可以看出 $x$ 变成 $-x$ 或 $2m - x$。最后可能会变成 $LR$,$L$,$R$,空四种情况。看 size 讨论一下即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n, m; std ::cin >> n >> m; std::vector<int> a(n), id(n); std ::string s (n, '0' ) ; for (auto &x : a) std ::cin >> x; for (auto &c : s) std ::cin >> c; std ::iota(id.begin (), id.end (), 0 ); std ::sort(id.begin (), id.end (), [&](int i, int j) { return a[i] < a[j]; }); std ::vector <int > A[2 ], ans(n); for (auto x : id) { int bit = a[x] & 1 ; if (s[x] == 'R' ) A[bit ].emplace_back(x); else if (!A[bit ].empty() && s[A[bit ].back()] == 'R' ) { ans[A[bit ].back()] = ans[x] = (a[x] - a[A[bit ].back()]) / 2 ; A[bit ].pop_back(); } else A[bit ].emplace_back(x); } for (int bit = 0 ; bit < 2 ; ++bit ) { while ((int )A[bit ].size () > 1 ) { int x = A[bit ].back(); A[bit ].pop_back(); if (s[x] == 'R' && s[A[bit ].back()] == 'R' ) { ans[x] = ans[A[bit ].back()] = m - (a[x] + a[A[bit ].back()]) / 2 ; A[bit ].pop_back(); } else { A[bit ].emplace_back(x); break ; } } int now = 0 ; for (; now + 1 < (int )A[bit ].size () && s[A[bit ][now + 1 ]] == 'L' ; now += 2 ) { ans[A[bit ][now]] = ans[A[bit ][now + 1 ]] = (a[A[bit ][now]] + a[A[bit ][now + 1 ]]) / 2 ; } if (A[bit ].size () & 1 ) ans[A[bit ].back()] = -1 ; else if (!A[bit ].empty() && s[A[bit ].back()] == 'R' ) { ans[A[bit ][now]] = ans[A[bit ][now + 1 ]] = m + (a[A[bit ][now]] - a[A[bit ][now + 1 ]]) / 2 ; } } for (auto x : ans) std ::cout << x << ' ' ; std ::cout << '\n' ; } return 0 ; }
D:以为最小费用最大流能过,结果 TLE 26 首先注意到如果有 $k$ 个需要移动到另外 $k$ 个地方,那么排序之后依次顺序丢进去即可。
然后设 $dp[i][j]$ 表示考虑前 $i$ 选择 $j$ 个位置作为终点(即前 $j$ 个 1 要移动到前 $i$ 个位置的 0 的地方)的最小值。状态转移就很明显了,主要是想到用这种方式 DP 很难得,哎还是我太菜了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n; std ::cin >> n; std::vector<int> a(n + 1), pos; for (int i = 1 ; i <= n; ++i) { std ::cin >> a[i]; if (a[i]) pos.emplace_back(i); } int k = pos.size (); const int inf = 1e9 + 2 ; std ::vector <std ::vector <int >> dp (n + 1 , std ::vector <int >(k + 1 , inf)) ; for (int i = 0 ; i <= n; ++i) dp[i][0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { dp[i][0 ] = 0 ; for (int j = 1 ; j <= k; ++j) { dp[i][j] = dp[i - 1 ][j]; if (a[i] == 0 ) dp[i][j] = std ::min (dp[i][j], dp[i - 1 ][j - 1 ] + abs (pos[j - 1 ] - i)); } } std ::cout << dp[n][k] << '\n' ; return 0 ; }
E,F 没时间看(D 搞得心浮气躁) 题意:树上任意两点的距离定义它们最短路上的边权异或和,求任意两点距离的距离之和。
做法:首先肯定要一位位的处理,但是我做的时候太早的一位位的处理导致特别慢,然后 TLE,后来细节优化终于 2343ms 过了,然后发现别人特别快。再一分析,发现
所以可以先把 dist[1][i]
求出来,再按位处理就很快了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const int M = 1e9 + 7 ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n; std ::cin >> n; std::vector<std::vector<std::pair<int, LL>>> e(n + 1); for (int i = 1 , u, v; i < n; ++i) { LL w; std ::cin >> u >> v >> w; e[u].emplace_back(v, w); e[v].emplace_back(u, w); } std ::vector <LL> val (n + 1 ) ; std ::function<void (int , int )> dfs = [&](int u, int fa) { for (auto &[v, w] : e[u]) if (v != fa) { val[v] = val[u] ^ w; dfs(v, u); } }; dfs(1 , 0 ); const int N = 60 ; std ::vector <int > cnt (N) ; for (auto x : val) { for (int i = 0 ; x; x >>= 1 , ++i) if (x & 1 ) ++cnt[i]; } int ans = 0 ; for (int i = 0 ; i < N; ++i) { ans = (ans + (1L L << i) % M * cnt[i] % M * (n - cnt[i])) % M; } std ::cout << ans << '\n' ; return 0 ; }
pdf 试题 的官方题解
A:用 Set 对区间进行贪心筛选,然后贪心查询即可 B:签到水题 C:贪心,从左到右,打标记,用下 Set 即可 D:双指针,树状数组或线段树区间加
这里同样可以用树状数组的思路优化成 $O(n)$
E:简单递推博弈 这个设此次取值为 x,后面先手期望为 f,然后列式子发现此次先手的期望为 $|x - f|$,然后从后往前递推即可
F:简单二分 G:很有意思的一道题 首先注意到(根出发)最短的那条路径必然全部要染色,然后我们可以设 $(d, u)$ 表示以 u 为根,每条路染色为 d 的方案数,注意到如果 u 不分叉(根节点特判一下),那么我们可以一直跑直到它分叉或者到叶子节点。如果 $d = dep[u]$,那么 u 必然要被染色,否则若分叉必然不被染色,若不分叉可能会被染色,这取决于最后能跑到哪里。
H:相当的毒瘤,要特别细心 首先把它变成 $1, -1$ 交替出现的形式,然后再看 $-1$ 的个数的奇偶性
特别注意,$-1, -1, -1, 1$ 要变成 $-1, 1, 1$ 而非 $1, -1, 1$
I:签到题 J:有意思的简单 DP 题
注意对称性
K:数论题 首先这个期望与每个排列的圈的个数有关。长度为 $n$ 的排列的圈的期望为 $a_n$,那么我们显然有
这个转移是注意到:点是退化的链,且一旦形成了圈就不再变了
然后用并查集维护一下即可。
N:签到题 A:签到题 C: 通过比较距离判断 D:特别经典有意思的最短路问题 题意是连的边必须满足一个编号是另一个的前缀。这样我们用 dist[u][dep]
表示 u
到 u >> len[u] - dep
的距离,显然 $len[u] \geq dep$ 才有意思。然后任意一点的距离就是看它们到它们的公共前缀之间的距离之和的最小值。
注意再 Dijkstra 的时候,我们找边要往下找!
F:经典数学问题!要不慌不忙推理即可 枚举 n 的值,然后整除分块加速即可
L:水题
我一开始以为题目给的是 S,导致无从下手
M:推理发现,答案始终为 0 B:直接暴力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (int i = 0 , sz; i < n; ++i) { std ::cin >> sz; for (int j = 0 , x; j < sz; ++j) { std ::cin >> x; a[i] |= 1 << --x; } } auto check = [&](int x) { for (int i = 0 ; i < n; ++i) { if (x & (1 << i)) { if ((x & a[i]) == a[i]) return 0 ; } else { if ((x & a[i]) != a[i]) return 0 ; } } return 1 ; }; int ans = 0 ; for (int i = 0 ; i < (1 << n); ++i) ans += check(i); std ::cout << ans << '\n' ; return 0 ; }
我这写法优雅的不行,可以让题目中 N 放宽到 25,且不用对 S 有任何限制。(写的时候没输入 x,然后 WA 几次才发现,也是服了自己了)
D 简单二分 H. 8 Game 超级经典的游戏!假搜索,真 Dijkstra 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); std ::vector <std ::pair<int , int >> dir = {{-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 }}; auto check = [&](int x, int y) { return x >= 0 && x < 3 && y >= 0 && y < 3 ; }; std ::map <std ::string , int > mp; std ::vector <std ::string > pm; std ::string od ("012345678" ) ; do { mp[od] = pm.size (); pm.emplace_back(od); } while (std ::next_permutation(od.begin (), od.end ())); std ::string s (9 , '0' ) ; for (int i = 0 ; i < 9 ; ++i) std ::cin >> s[i]; std ::vector <int > c (9 ) ; for (int i = 1 ; i < 9 ; ++i) std ::cin >> c[i]; std ::vector <LL> dist (pm.size (), INT64_MAX) ; auto Dijkstra = [&](int s) { dist[s] = 0 ; std ::priority_queue<std ::pair<LL, int >> Q; Q.push({0 , s}); while (!Q.empty()) { auto [du, u] = Q.top(); Q.pop(); if (dist[u] != -du) continue ; std ::string now = pm[u]; int p0 = std ::find (now.begin (), now.end (), '0' ) - now.begin (); std ::pair<int , int > zero{p0 / 3 , p0 % 3 }; for (auto [dx, dy] : dir) if (check(zero.first + dx, zero.second + dy)) { int q0 = (zero.first + dx) * 3 + zero.second + dy; std ::string cur = now; std ::swap(cur[p0], cur[q0]); int v = mp[cur], cost = c[cur[p0] - '0' ]; if (dist[v] > dist[u] + cost) { dist[v] = dist[u] + cost; Q.push({-dist[v], v}); } } } }; Dijkstra(mp["123456780" ]); std ::cout << dist[mp[s]] << '\n' ; return 0 ; }
注意要保存成字符串节省空间,还有就是用排列缩小编号
I:裸动态开点线段树,一开始以为 multiset 能过(pb_ds 无敌)
应该 long long 的地方 写成了 int 导致 WA 两发 很不应该
multiset 不能过是因为 distance 求的时候是 $O(N)$ 的,然而 pb_ds 就提供 $O(\log n)$ 的操作。但是 pb_ds 默认不支持 multiset 的形式,于是用 std::pair<int, int>
, 后面带指标就可以变得像 multiset 了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;#include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/tree_policy.hpp> using namespace __gnu_pbds;#define ordered_set tree<std::pair<LL, int>, null_type, std::less<>, rb_tree_tag, tree_order_statistics_node_update> int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n, q; std ::cin >> q >> n; std ::vector <LL> a (n) ; ordered_set A; for (int i = 0 ; i < n; ++i) A.insert({a[i], i + 1 }); int cnt = n; while (q--) { int op; LL k; std ::cin >> op; if (op == 1 ) { int id; std ::cin >> id >> k; A.erase(A.lower_bound({a[id], 0 })); a[id] += k; A.insert({a[id], ++cnt}); } else { std ::cin >> k; std ::cout << n - A.order_of_key({k + 1 , 0 }) << '\n' ; } } return 0 ; }
竟然比我用动态开点线段树快,我不能接受
加分了但是不太光彩的一场!本质上我只做了 2 题(5 题只做两题,哎)C 题最终评测的时候发现有 询问次数可能为 $2n$,然后最后我攻击了自己的代码。
A 特判一下 $b == 1$ 然后输出 $a, a b, a(b + 1)$ 即可
B 找到最小值,然后往两边递增即可
C 首先注意到 $\max(1, x) = x, \min(n, x) = x$,然后先通过比较 $1, 2$,$1, 3$ 就可以确定某一个值,然后根据这个值与 $\frac{n}{2}$ 的大小关系来确定是询问大的还是询问小的。总次数不超过为 $\frac{3n}{2} + 4$
D 一开始想错了,以为只要把度数大于 2 的剪掉放在最后面即可。后来发现剪去两边度都大于 2 的才是最优的选择。然后根据题解做了,注意要先处理儿子,最后再处理自己
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::vector <std ::set <int >> g (n + 1 ) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; g[u].insert(v); g[v].insert(u); } std ::vector <int > son (n + 1 ) ; for (int i = 1 ; i <= n; ++i) son[i] = g[i].size () - 1 ; ++son[1 ]; std::vector<std::set<std::pair<int, int>>> e(n + 1); for (int i = 1 ; i <= n; ++i) { for (auto x : g[i]) e[i].insert({-son[x], x}); } std ::vector <std ::pair<int , int >> cut, add; std ::function<void (int , int )> dfs = [&](int u, int fa) { for (auto [_, v] : e[u]) if (v != fa) dfs(v, u); if (son[u] >= 2 ) { if (g[u].count(fa)) { g[u].erase(fa); g[fa].erase(u); cut.emplace_back(u, fa); --son[fa]; } } for (auto [_, v] : e[u]) if (v != fa && son[u] > 2 ) { if (g[u].count(v)) { g[u].erase(v); g[v].erase(u); cut.emplace_back(u, v); --son[u]; } } }; dfs(1 , 0 ); std ::vector <int > vis (n + 1 ) ; std ::vector <std ::pair<int , int >> p; std ::vector <int > tmp; std ::function<void (int , int )> find = [&](int u, int fa) { vis[u] = 1 ; if (g[u].size () <= 1 ) tmp.emplace_back(u); for (auto v : g[u]) if (v != fa) { find (v, u); } }; for (int i = 1 ; i <= n; ++i) if (!vis[i]) { tmp.clear (); find (i, 0 ); p.push_back({tmp[0 ], tmp.back()}); } for (int i = 1 ; i < p.size (); ++i) { add.emplace_back(p[i - 1 ].second, p[i].first); } int sz = cut.size (); std ::cout << sz << '\n' ; for (int i = 0 ; i < sz; ++i) { std ::cout << cut[i].first << ' ' << cut[i].second << ' ' << add[i].first << ' ' << add[i].second << '\n' ; } } return 0 ; }
E 找规律,规律找到了后,代码就好些了,直接看题解的图一目了然
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int cas = 1 ; std ::cin >> cas; auto f = [](int n) { return (n + 1 ) / 2 * n; }; auto g = [](int n) { return n * n - (n / 2 ) * (n / 2 ); }; while (cas--) { int m, k; std ::cin >> m >> k; std::vector<std::pair<int, int>> a(k); for (int i = 0 , x; i < k; ++i) { std ::cin >> x; a[i] = {x, i + 1 }; } std ::sort(a.begin (), a.end (), std ::greater<>()); int l = 0 , r = 1e4 , mx = a[0 ].first; while (l <= r) { int mid = (l + r) / 2 ; if (f(mid) >= mx && g(mid) >= m) r = mid - 1 ; else l = mid + 1 ; } int n = l; std ::vector <std ::vector <int >> b (n, std ::vector <int >(n)) ; int now = 0 ; for (int i = 1 ; i < n; i += 2 ) { for (int j = 0 ; j < n; j += 2 ) { while (now < a.size () && a[now].first == 0 ) ++now; if (now == a.size ()) break ; b[i][j] = a[now].second; --a[now].first; } if (now == a.size ()) break ; } for (int i = 0 ; i < n; i += 2 ) { for (int j = 0 ; j < n; j += 2 ) { while (now < a.size () && a[now].first == 0 ) ++now; if (now == a.size ()) break ; b[i][j] = a[now].second; --a[now].first; } if (now == a.size ()) break ; } for (int i = 0 ; i < n; i += 2 ) { for (int j = 1 ; j < n; j += 2 ) { while (now < a.size () && a[now].first == 0 ) ++now; if (now == a.size ()) break ; b[i][j] = a[now].second; --a[now].first; } if (now == a.size ()) break ; } std ::cout << n << '\n' ; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < n; ++j) { std ::cout << b[i][j] << " \n" [j == n - 1 ]; } } } return 0 ; }
E: 起初想的 dp 是 $n^4$ 方的,后面想到了 $O(n^3)$ 的做法以为也是 $O(n^4)$ 的就没写 设 dp[i][j]
表示长度为 i,有 j 个位置没有主动开启的方案数,首先 $dp[i][0] = 2^{i - 1}$ 这是因为我们考虑第一个位置,后面的只能在两边选,然后就是一个二进制的和正好是 $2^{i - 1}$,然后我们可以枚举第一个没有主动开启的位置。所以
有 $O(n^2)$ 的做法
I 找一个长为 $n$ 的排列 $x_1, \cdots, x_n$ 使得 $\displaystyle \prod_{j = 1}^{i} \mod n$ 正好是 $0, \cdots, n - 1$ 的一个排列。
做法 1:注意到除了 4 一个合数外,其它合数都做不到这一点,因为最终乘积必然是 0,非 4 的合数中间必然会出现 0。所以剩下的只需看 $n$ 为素数的情况,注意到 $\frac{i}{i - 1} = \frac{j}{j - 1}$ 当且仅当 $i = j$, 因此这就是我们的方案了。
做法 2:当然了,还有原根的做法,即把乘变成加。此时我们考虑 $g^0, g^{1}, g^{n - 1 - 2}, g^{3}, \cdots$ 也是一个可行方案,得到的前缀和恰好是 $0, 1, -1, 2, -2, \cdots$
B $i, j$ 写反,debug 了 15 分钟,真的烦!导致后面用时都加了
C 超级有趣感觉,找找规律就会发现往下一跳对角线,规模就变小了,然后跑 n 次好了
D 标准 DP 没啥好说的
E 推理一下 10 分钟,就知道怎么做,但是这个代码实现是真的把自己给整吐了,这个时候就要参考一下 dreamoon_love_AA 的做法了。 用全局变量加数组,然后 i 从 1 开始,这样会让很多东西简单不少(但是我还是不想写)。
mohammedehab2002 的连续第二场,相对第一次质量更高,不只是单纯的数论
B: 没注意最多 两个,贡献了一次 WA 如果最后只有两个,那么必然要求所有数异或和 $r = 0$ 0,如果是 3 个,每一个异或和部分都是 r。然后这正好是奇偶的情况。所以 $r = 0$ 直接就可以,否则 $r \neq 0$,那么我们碰到异或前缀和为 $r$ 就删掉并计数一次,然后看最终结果是不是 r,且计数次数大于 1
很有意思的问题
C:背包 + 奇偶性 D: 经典问题:区间划分成子区间,多次查询 合理区间等价于同一区间不能有公共的素因子。所以如果不是多次查询,我们直接贪心就能做了。多次查询我搞不了,就以为要莫队,结果学了莫队也不能转移。后来才知道是倍增算法!!
做法:预处理 N 以内所有素数,然后处理出前 N 个数的所有素因子(不算重),再 dp[i]
表示能到达右边的最远位置,即 $[i, dp[i])$ 是以 i 开头最大的合理区间。那么 dp 的时候从右往左转移即可。dp[i] 定义好了,我们就朴素倍增即可。倍增的最大高度显然不会超过 log n,因此算法复杂度为 $O(N \log N)$。
E: 经典问题:经过 k 次互换操作,最终得到的不同排列的数量 首先把它简化成,有多少个长度为 n 排列正好经过 k 次互换操作能变成有序的记作 $DP[n][k]$,那么状态转移为
最终的答案是 $dp[n][j] + dp[n - 2][j] + \cdots$。复杂度 $O(n k)$,并且我们可以优化空间为 $O(1)$ 的。但是时间复杂度还是过不了,因此我们需要寻找其它做法:
注意到 $k$ 次互换操作后,最多有 $2k$ 个位置发生了改变。
我们用生成函数 $\displaystyle f_n = \sum_{k \geq 0} dp[n][k] x^k$,注意 $f_1 = 1, f_2 = 1 + x$
然后就转换成计算这个函数的次数不高于 k 的系数了。分治做法: $L = \sum_{i = l}^{m - 1}(1 + ix), R = \sum_{i = m}^{r - 1} (1 + ix)$ 所以可以借助于 $L$ 来计算 $R$。完全可以类似于 powMod 的计算。
hugin 的代码实现
另外的做法见我的 codeforces 上的评论 以及我的代码实现
mohammedehab2002 的连续第一场
C: 拓展 Wilson 定理 即使不用这个定理也行。原理: 首先不能放与 $n$ 不互素的元素,否则乘积必然不与 $n$ 互素,从而不可能为 $1$,把剩下的数乘起来,如果为 $1$ 就好了,不为 $1$,必然是其中一个,把那一个踢了就行。
拓展 Wilson 定理:
即 n 为原根时为 -1, 其它情况为 1.
我们判断的使用用 $= n - 1$ 来判断结果 $n = 2$ 的时候给了我一次 WA
D:概率做法真的骚气 区间有超过一半的元素就才会计数,因此,我们可以概率枚举 50 次。当然了常规做法是主席树(不会)会计数的众数必然是中位数。也可以用莫队。
很有趣的一个堆积问题,关键在于转化成简单的四则运算,而当时没有想到这样的运算,可惜
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;LL powMod (LL x, LL n, LL p) { LL r = 1 ; while (n) { if (n&1 ) r = r * x % p; n >>= 1 ; x = x * x % p; } return r; } namespace Binom {int N = 0 ;LL M = 1e9 + 7 ; std ::vector <LL> fac, ifac;void setMod (LL m) { M = m; fac[0 ] = 1 ; for (int i = 1 ; i < N; ++i) fac[i] = fac[i - 1 ] * i % M; ifac[N - 1 ] = powMod(fac[N - 1 ], M - 2 , M); for (int i = N - 1 ; i; --i) ifac[i - 1 ] = ifac[i] * i % M; } void init (int n, LL m = M) { N = n; fac.resize(N); ifac.resize(N); setMod(m); } LL binom (int n, int k) { if (n < 0 || n < k) return 0 ; return fac[n] * ifac[k] % M * ifac[n - k] % M; } LL lucas (LL n, LL k) { LL r = 1 ; while (n && k) { int np = n % M, kp = k % M; if (np < kp) return 0 ; r = r * binom(np, kp) % M; n /= M; k /= M; } return r; } } int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n; std ::cin >> n; std ::map <char , int > mp{{'R' , 0 }, {'W' , 1 }, {'B' , 2 }}; Binom::init(3 , 3 ); int ans = 0 ; std ::string s; std ::cin >> s; for (int i = 0 ; i < n; ++i) { ans += mp[s[i]] * Binom::lucas(n - 1 , i); } ans %= 3 ; if (n % 2 == 0 ) ans = -ans; std ::cout << "RWB" [ans < 0 ? ans + 3 : ans] << '\n' ; return 0 ; }
C 意识到此问题跟原始位置无关就不难想到先排序,然后区间 DP
D 被卡死了。首先注意到如果两个序列中 1 的个数都不小于 n,那么就存在序列的分别的子序列包含它们。然后就好了。
我一开始搞成树上问题 TLE。有点可惜。并查集常数确实小很多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::cin .tie(nullptr )->sync_with_stdio(false ); int n, m; std ::cin >> n >> m; std::vector<int> p(n + 1), fa(n + 1), c(n + 1); for (int i = 1 ; i <= n; ++i) std ::cin >> p[i], fa[i] = i; for (int i = 0 , x; i < m; ++i) { std ::cin >> x; ++c[x]; } std ::function<int (int )> find = [&](int x) { return x == fa[x] ? x : fa[x] = find (fa[x]); }; auto merge = [&](int x, int y) { fa[find (x)] = find (y); }; for (int i = 1 ; i <= n; ++i) if (c[i] == 0 ) merge(i, p[i]); LL ans = 0 ; for (int i = n; i >= 0 ; --i) { int x = find (i); if (c[x]) { ans += i; if (--c[x] == 0 ) merge(x, p[x]); } } std ::cout << ans << '\n' ; return 0 ; }
质量还是很不错的。官方题解
A:简单线性代数,写的是否 f(a, b)
写成 f(a, d)
,然后一直出问题了 B:知道怎么处理,但是写的话很麻烦 最后终于磨出来了,从尾部开始考虑,然后判断即可。题目中 n 可以变得更大。
C: 送分题 D: 阶乘的 p 因子指数 E: 树上两点距离 F: 得到线性关系,然后一个一元二次方程即可 G: 分情况讨论一下 kczno1 的 AC 代码
本来想自动向量化暴力做这个问题,后来发现还是不行。
I:送分题,但是 我的第一次提交 有 bug 也过了(ba, aa) 应该是 NO J: 求 $\sum_{i = 1}^n \frac{1}{i}$ 求上述结果时,若 $n$ 特别大,可以求 $\sum_{i = 1}^n \frac{1}{i} \simeq \sum_{i = 1}^N \frac{1}{i} + \ln n - \ln N$
K: 仅有几个是可以的,特判一下即可 E:用概率是最好的理解 分别考虑一整行 o 和 一整列 o 的情况。
F:如何求一个计算式(不懂) G:暴力黑科技能搞,但是标准做法是每一位考虑 每一位考虑的好处是有很多减了之后不影响答案。
1498 :很不错的一场印度赛A: 直接暴力即可,注意到 3 的特殊性,所以最多 3 次 B: 直接贪心即可,然后用 multiset 完美契合 C:这题我卡了挺久主要是题意不明,很烦,明白题意后计算 D(k) 的个数就行 D:直接暴力 $O(n m^2)$,注意到一个点被访问后,再访问就没啥意义了。所以从后往前遍历,每个点最多访问两次,最终复杂度为 $O(n m)$ E:这题不需要交互就能做?此情况度数最小的度数前缀和为 $\frac{n(n-1)}{2}$,竟然就能缩点 F:这个经典博弈,贼 6 C:注意到最多只有一个超过 $\lceil \frac{m}{2} \rceil$ 然后对出现频率最高的分情况讨论即可 E:我知道一致轮换模拟,注意到相邻位置 gcd 不为 1 的话,以后也永远不为 1,所以把为 1 的位置用 set 存一下然后更新即可 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std::vector<int> a(n), nxt(n); for (auto &x : a) std ::cin >> x; for (int i = 0 ; i < n; ++i) nxt[i] = i + 1 ; nxt.back() = 0 ; std ::set <int > S; for (int i = 0 ; i < n; ++i) if (std ::gcd(a[i], a[nxt[i]]) == 1 ) S.insert(i); std ::vector <int > ans; int x = -1 ; while (!S.empty()) { auto it = S.upper_bound(x); if (it == S.end ()) it = S.upper_bound(-1 ); x = *it; ans.emplace_back(nxt[x]); S.erase(nxt[x]); nxt[x] = nxt[nxt[x]]; if (std ::gcd(a[x], a[nxt[x]]) != 1 ) S.erase(x); } std ::cout << ans.size (); for (auto x : ans) std ::cout << ' ' << x + 1 ; std ::cout << '\n' ; } return 0 ; }
E:单调栈优化 DP 设 f[i]
为 前 i 个数的答案。那么 $f[i] = \max_{j_0 < j < i} (b[i] + f[j], f[j_0])$ 其中 $j_0$ 为高度小于 i 的最大位置。于是我们用单调栈优化 DP 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std::vector<int> h(n + 1), b(n + 1); for (int i = 1 ; i <= n; ++i) std ::cin >> h[i]; for (int i = 1 ; i <= n; ++i) std ::cin >> b[i]; h[0 ] = INT_MIN; std ::stack <int > S; S.push(0 ); std::vector<LL> mx(n + 1, INT64_MIN), f(n + 1); for (int i = 1 ; i <= n; ++i) { mx[i] = f[i - 1 ]; while (h[S.top()] > h[i]) { mx[i] = std ::max (mx[i], mx[S.top()]); S.pop(); } f[i] = std ::max (mx[i] + b[i], S.top() == 0 ? INT64_MIN : f[S.top()]); S.push(i); } std ::cout << f[n] << '\n' ; return 0 ; }
题意:给定一个 0-1 主串 S 和模式串 T,$|T| \leq |S|$ 问 $S$ 的长度为 $|T|$ 的子串和 $T$ 互异的位置数的最小值。
显然 $f[j] = \sum_{i = 0}^{|T| - 1} s_{i+j} \wedge t_i$,这是一个减法卷积,OK 结束。
如果字符串是小写字母组成,那么这个问题就可以枚举每一个字符,看相同的位置!然后累加 $O(26 \cdot |S| \log |S|)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::string s, t; std ::cin >> s >> t; std ::vector <LL> a[2 ], b[2 ]; for (auto x : s) { a[0 ].emplace_back(x - '0' ); a[1 ].emplace_back('1' - x); } for (auto x : t) { b[0 ].emplace_back(x - '0' ); b[1 ].emplace_back('1' - x); } Poly A = Poly(a[0 ]).mulT(Poly(b[1 ])) + Poly(a[1 ]).mulT(Poly(b[0 ])); auto r = A.a; r.resize(s.size () - t.size () + 1 ); LL ans = t.size (); for (auto x : r) ans = std ::min (ans, x); std ::cout << ans << '\n' ; return 0 ; }
B:删除不连续的点之后是有序的 我也没有想到我会被这题卡,真的难受。我以为只要删除了前置 0 和后置 1 之后,没有连续的 0 或 1 即可了。但是忽略了 100110
这种 case。WA 的心态爆炸,WA 的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;bool solve () { std ::string s; std ::cin >> s; int n = s.size (), i = 0 ; while (n > 0 && s[n - 1 ] == '1' ) --n; while (i < n && s[i] == '0' ) ++i; if (i == n) return 1 ; int cnt[2 ] {}; for (int j = i + 1 ; j < n; ++j) if (s[j] == s[j - 1 ]) { ++cnt[s[j] - '0' ]; } return cnt[0 ] == 0 || cnt[1 ] == 0 ; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { std ::cout << (solve() ? "YES\n" : "NO\n" ); } return 0 ; }
下面是正确的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;bool solve () { std ::string s; std ::cin >> s; int n = s.size (), now = 1 ; for (int i = 1 ; i < n; ++i) if (s[i] == s[i - 1 ]) { if (s[i] - '0' == now) { if (now == 0 ) return false ; --now; } } return true ; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { std ::cout << (solve() ? "YES\n" : "NO\n" ); } return 0 ; }
C:经典问题 一开始就知道 奇偶要分开考虑,WA 到吐了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; auto f = [&](std ::vector <LL> &a) -> std ::vector <LL> { int sz = a.size (); std ::vector <LL> b (sz) ; LL sm = 0 , mn = INT64_MAX; for (int i = 0 ; i < sz; ++i) { sm += a[i]; mn = std ::min (mn, a[i]); b[i] = sm + mn * (n - 1 - i); } return b; }; std ::vector <LL> a[2 ]; for (int i = 0 , x; i < n; ++i) { std ::cin >> x; a[i & 1 ].emplace_back(x); } std ::vector <LL> sa[2 ] = {f(a[0 ]), f(a[1 ])}; LL r = INT64_MAX; for (int i = 1 ; i < n; ++i) { r = std ::min (r, sa[0 ][i / 2 ] + sa[1 ][(i - 1 ) / 2 ]); } std ::cout << r << '\n' ; } return 0 ; }
D: gcd 类问题,还是好做的 E:动态规划 设 dp[i][j][0/1]
为 x 以 i 结尾, y 以 j 结尾,最终它们是否以 x 结尾的总答案数。有了这个状态,状态转移就明显了。 代码请参考 dlalswp25 的代码
C2:k = 3 的情况很早就知道了,然后一般的 k 想了半天,突然发现添加 1 即可 D:这个 DP 做法我自己应该是想不到的 E:E1 贪心即可,然后 E2 又是个 DP 我们考虑 Dp[i][j]
为删除 j 个节点后,前 i 个最少可以分成多少个。那么显然 $DP[i][j] = \min DP[i_x][j - x]$,其中 $i_x, \cdots, i$,$i_x$ 删除 x 个 点后, $i_x, \cdots, i$ 所有数互异的最小的值。然后我们可以先求 $i_x$,求的时候注意细节,否则 TLE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); const int N = 1e7 + 2 ; std::vector<int> f(N), mp(N); for (int i = 1 ; i * i < N; ++i) { int ii = i * i, cur = 0 ; for (int j = ii; j < N; j += ii) f[j] = ++cur; } int cas = 1 ; std ::cin >> cas; while (cas--) { int n, k; std ::cin >> n >> k; std ::vector <int > a (n) ; for (auto &x : a) { std ::cin >> x; x = f[x]; } std ::vector <std ::vector <int >> left (n, std ::vector <int >(k + 1 )) ; for (int j = 0 ; j <= k; ++j) { int cnt = 0 , now = 0 ; for (int i = 0 ; i < n; ++i) { if (++mp[a[i]] > 1 ) { if (++cnt > j) { while (--mp[a[now]] == 0 ) ++now; ++now; --cnt; } } left[i][j] = now; } for (int i = now; i < n; ++i) --mp[a[i]]; } std ::vector <std ::vector <int >> dp (n, std ::vector <int >(k + 1 , n)) ; for (int j = 0 ; j <= k; ++j) dp[0 ][j] = 1 ; for (int i = 1 ; i < n; ++i) { for (int j = 0 ; j <= k; ++j) { for (int x = 0 ; x <= j; ++x) { dp[i][j] = std ::min (dp[i][j], left[i][x] > 0 ? dp[left[i][x] - 1 ][j - x] + 1 : 1 ); } } } std ::cout << dp[n - 1 ][k] << '\n' ; } return 0 ; }
首先答案必然是 $\sum_{i = 1}^n |a_i - b_i|$。然后就是看分配策略了,注意到如果 $b_i < a_i$,且左边都分配好了,那么必然就可以直接分配了,否则就会出现 $a_i \leq b_i \leq b_{i + 1} \leq a_{i + 1}$ 的局面,此时对比一下答案即可。
1500 :第一题都不会做人直接傻了A:首先注意到如果有两组两个相同的,那么这四个数就是答案,否则直接暴力,由抽屉原理,很快就能解决。
B:本质就是一个拓展 gcd + 中国剩余定理 + 二分,然后注意简化一下每次二分的部分从而优化代码。
F:概率 + 三分 不难推出答案为 $f(x) = \frac{n x + m}{1 - q^x}$ 的整数点最小值。单峰函数所以可以三分。
J:表面 Nim sg 问题,实则吉老师线段树 用一个线段树的最小值和次小值和最小值的个数就可以维护更新最大值。
K: 简单找规律签到水题 L:排序水题 M:树上背包(特别有意思) 只做了两题,不看题解我应该做不了 3 题,如果去攻击代码能加分的
B:很快就知道只有唯一的最大值点可能成为胜决策点,然后如果两边不平均,必然不是,而且最大递增长度要为奇数(忽略了导致两次 WA) C:三等分分别处理(三分?) 确实是个好问题。可惜没有往这个方向想 1493 :记错时间没参加,否则真掉分了D:gcd 可看作素因子分解的每个因子指数的最小值,实现时要注意效率选择合适的数据结构 E: XOR 神奇的找规律(我应该是看不出来的) 注意到最高位的答案和 r 的奇偶性有很大的关系!
一场不看题解几乎一题不会的场
A:经典转化 即使看了题解,说等价于删除左或者删除右,我一开始还是没懂,后来注意到每次有 2N - 2 种选择之后就懂了。后来就是删除然后递推了(个人认为是平凡的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;LL powMod (LL x, LL n, LL p) { LL r = 1 ; while (n) { if (n&1 ) r = r * x % p; n >>= 1 ; x = x * x % p; } return r; } namespace Binom {int N = 0 ;LL M = 1e9 + 7 ; std ::vector <LL> fac, ifac;void setMod (LL m) { M = m; fac[0 ] = 1 ; for (int i = 1 ; i < N; ++i) fac[i] = fac[i - 1 ] * i % M; ifac[N - 1 ] = powMod(fac[N - 1 ], M - 2 , M); for (int i = N - 1 ; i; --i) ifac[i - 1 ] = ifac[i] * i % M; } void init (int n, LL m) { N = n; fac.resize(N); ifac.resize(N); setMod(m); } LL binom (int n, int k) { if (n < 0 || n < k) return 0 ; return fac[n] * ifac[k] % M * ifac[n - k] % M; } LL lucas (LL n, LL k) { LL r = 1 ; while (n && k) { int np = n % M, kp = k % M; if (np < kp) return 0 ; r = r * binom(np, kp) % M; n /= M; k /= M; } return r; } } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; const LL M = 998244353 ; Binom::init(n, M); std ::vector <LL> x (n) ; x[0 ] = 1 ; for (int i = 1 ; i < n; ++i) x[i] = x[i - 1 ] * (2 * i - 1 ) % M; LL r = 0 ; for (int i = 0 ; i < n; ++i) if (a[i] == 1 ) { r += Binom::binom(n - 1 , i) * x[i] % M * x[n - 1 - i] % M; } std ::cout << r % M << "\n" ; return 0 ; }
C:经典 FFT(神仙题) 这不看题解咋做呀,看了题解也要搞半天。题解是这样审的:
首先考虑这个问题:给定非负整数 $W, A, B$ 问从格点 $(0, 0)$ 到格点 $(W, AW + B)$ 有多少条路径(只往右上放走)并且不许在直线 $y = Ax + B$ (严格的)上方。
可以证明答案是 $\binom{W + AW + B}{W} - A \binom{W + AW + B}{W - 1}$。这个证明完全类似 Catalan 数的计算。
接下来,我们给定非负整数 $H, W, A, B$ 满足 $0 \leq B \leq AW + B \leq H$,考虑从 $(0, 0)$ 到 $(W, H)$ 的格点路径。我们定义路径的权重为经过形如 $(x, Ax+B)$ 的点的个数。我们把所有路径的权重和记作 $f(H, W, A, B)$。考虑 $z = f(H, W, A, B) - A \cdot f(H + 1, W - 1, A, B)$,显然
考虑从 $(0, 0)$ 到 $(W, H + 1)$ 的路径个数。因为 $H + 1 > AW + B$,因此总存在最小的 $x$ 使得路径经过 $(x, Ax + B)$ 并且此后都严格在直线 $y = Ax + B$ 之上。把这后一段往下带一手,那么就是不严格在直线之上了(我们强行演算也可以)。即 $z = \binom{W + H + 1}{W}$,因此
注意到上述结果于 B 无关,因此我们不妨取 $B = 0$ 这很重要!。
我们回到原问题:我们需要计算 $\sum_{1 \leq x \leq N} g(x)$ 其中 $N = \lfloor \frac{R}{B}\rfloor$,$g(x) = (R + 1 - Bx) f(R, B, x)$ 注意到 $(x, y)$ 会被直线 $Ax + B$ 经过 $\frac{y}{x}$ 次!因此就是上述计算!
卡在时间上过了,差点超时。
D:经典 FFT(神仙题) 官方题解说是使用 Tellegen’s Principle ,但是 jtnydv25 给了更简洁的做法。SevenDawns 的 Talk。 利用 多点求值新科技 类似的推出,我们需要我们需要求解
其中 $T(x) = \sum_{i = 1}^n \frac{C_i}{1 - A_i x}$。我们记 $P_{[L, R]} = \prod_{i = L}^{R} (x - B_i)$。我们分治就可以得到答案
代码太长不贴了。
G: 可选多堆的二人公平博弈 问题转化成求多元多项式的 k 次方的问题。这里多元多项式是比较特殊的,而且它的乘法也比较特殊。即
它是 fwt 变换可解决的一种。当然了这也可以如果 $N = 1$ 这就是实打实的卷积,并且这个长度不可拓展!那么我们可以每一维做一次 DFT。
I 此题真的把我折磨吐了(最后开始 Coach 模式终于解决了)。注意到最后一个元素的特殊性!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const LL M = 998244353 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std ::vector <int > vis (n) ; std ::vector <int > a (m) ; for (auto &x : a) { std ::cin >> x; vis[--x] = 1 ; } int k = 1 ; while (k < m && a[k] > a[k - 1 ]) ++k; LL r = 1 ; for (int i = 0 , cnt = 0 , j = 0 ; i < n; ++i) { if (j < k && i == a[j]) { if (++j == k) ++j; } if (vis[i]) continue ; r = r * (j + cnt) % M; ++cnt; } std ::cout << r << "\n" ; return 0 ; }
首先注意到如果 u 到 v 和 v 到 u 有相同的 label,那么必然 YES,否则如果 u 到 v 和 v 到 u 都有边,那么偶数情况必然 YES,奇数情况 NO。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std ::map <std ::pair<int , int >, char > mp; int cnt[2 ]{}; for (int i = 0 ; i < m; ++i) { char op; std ::cin >> op; if (op == '?' ) { int k; std ::cin >> k; std ::cout << ((cnt[0 ] || (cnt[1 ] && k % 2 == 1 )) ? "YES\n" : "NO\n" ); } else { int u, v; std ::cin >> u >> v; if (op == '+' ) { char c; std ::cin >> c; mp[{u, v}] = c; if (mp.count({v, u})) { ++cnt[1 ]; if (mp[{v, u}] == c) ++cnt[0 ]; } } else { if (mp.count({v, u})) { --cnt[1 ]; if (mp[{v, u}] == mp[{u, v}]) --cnt[0 ]; } mp.erase({u, v}); } } } return 0 ; }
1494F :拓展 Euler 路先走 Euler 路,然后走两点折返的路径(先删去两点折返路,细节就是删完之后要保证连通)
我最后攻击了自己的代码 …
以上代码还有很多可优化的地方,不过暂时没时间也没必要优化。最终无比丑陋的代码
1491E :经典树上问题,Fib 树大致题意:一颗树 Fib 树当且仅当它的节点个数为 Fib 数且可以拆成两颗 Fib 树。
做法:首先所有节点个数必须是 Fib 数 $F_n$,且可以拆成有两颗 $F_{n - 1}$ 和 $F_{n- 2}$ 的子树。我们递归的看这两颗子树是不是 Fib 树即可。若有多种分拆方式,可以证明它们不会影响结果。(可画图看看就能理解,详细说明还挺麻烦)
比赛的时候知道这么做,但是怕递归超时就不敢这么写,哎!还想着怎么搞出更简洁的公式,实际上复杂度是 $O(n F_n)$ 的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <std ::set <int >> e (n) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; --u; --v; e[u].insert(v); e[v].insert(u); } std ::vector <int > fib{1 , 1 }; while (fib.back() < n) fib.emplace_back(fib[fib.size () - 2 ] + fib[fib.size () - 1 ]); if (fib.back() != n) { std ::cout << "NO\n" ; return 0 ; } std::vector<int> sz(n), fa(n); bool flag = true ; std ::function<void (int , int )> solve = [&](int u, int i) { if (i <= 3 || !flag) return ; int r = -1 ; std ::function<void (int )> dfs = [&](int u) { sz[u] = 1 ; for (auto v : e[u]) if (v != fa[u]) { fa[v] = u; dfs(v); sz[u] += sz[v]; } if (sz[u] == fib[i - 1 ] || sz[u] == fib[i - 2 ]) { r = u; } }; fa[u] = -1 ; dfs(u); if (r == -1 ) { flag = false ; return ; } e[r].erase(fa[r]); e[fa[r]].erase(r); if (sz[r] == fib[i - 1 ]) { solve(fa[r], i - 2 ); solve(r, i - 1 ); } else { solve(fa[r], i - 1 ); solve(r, i - 2 ); } }; solve(0 , fib.size () - 1 ); std ::cout << (flag ? "YES\n" : "NO\n" ); return 0 ; }
上述代码可以通过打标记来删边,不一定要用 set 或 unordered_set
存边。
大致题意:u 到 u + v 有一条有向边,当且仅当 $u \And v = v$。问 $x$ 能否到达 $y$。
做法:首先注意到若 $x > y$ 肯定不行,从 $x$ 出发的路径中二进制中 1 的个数只减不增。并且任何后缀的二进制中 1 的个数也是只减不增的。可以归纳证明此为 x 可达 y 的充要条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;bool solve () { int u, v; std ::cin >> u >> v; if (u > v) return 0 ; int x = 0 , y = 0 ; for (int i = 0 ; i < 30 ; ++i) { x += u % 2 ; y += v % 2 ; u /= 2 ; v /= 2 ; if (x < y) return 0 ; } return 1 ; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { std ::cout << (solve() ? "YES\n" : "NO\n" ); } return 0 ; }
大致题意:给定初始数组 S, 每次经过 i 后,下一步就会到达 $i + S_i$ 直到值大于 n,然后 $S_i$ 减 1,且不会小于 1。问至少多少次可以使得所有的 S 都变成 1。
做法:显然从左往右贪心即可。但是要延迟更新,不然 TLE,延迟更新写的时候,记得只记录往后走的一步。具体来讲用 b[i]
表示 i 位置已被前面的位置走了 b[i]
次。因此 ans += std::max(0, a[i] - b[i] - 1)
,然后 $i + 2, \cdots, i + a[i]$ 的 b[i]
都要加 1。直接这么做复杂度 $O(n^2)$,我们可以用线段树,或者 set,或者拓展树状数组优化到 $O(n \log n)$(不写了)。然而次问题可以优化到 $O(n)$(不可思议)做法本质和拓展树状数组思路一致。记 $c[i] = b[i] - b[i - 1]$ 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std::vector<LL> a(n), b(n + 1); for (auto &x : a) std ::cin >> x; LL r = 0 ; for (int i = 0 ; i < n; ++i) { for (int j = 2 ; j <= a[i] && i + j < n; ++j) ++b[i + j]; if (b[i] >= a[i]) { b[i + 1 ] += b[i] - a[i] + 1 ; } else { r += a[i] - b[i] - 1 ; } } std ::cout << r << "\n" ; } return 0 ; }
$O(n)$ 做法 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std::vector<LL> a(n + 1), c(n + 1); for (int i = 1 ; i <= n; ++i) std ::cin >> a[i]; LL r = 0 , b = 0 ; for (int i = 1 ; i <= n; ++i) { b += c[i]; if (b >= a[i]) { if (i + 1 <= n) { c[i + 1 ] += b - a[i] + 1 ; if (i + 2 <= n) c[i + 2 ] -= b - a[i] + 1 ; } } else { r += a[i] - b - 1 ; } if (i + 2 <= n) ++c[i + 2 ]; if (i + a[i] < n) --c[i + a[i] + 1 ]; } std ::cout << r << "\n" ; } return 0 ; }
二进制为 a 位 0 和 b 位 1 的两个数 x, y($x \geq y$,且无前导 0)。问是否存在 x, y 使得 $x - y$ 的二进制有且仅有 k 个 1。一开始想错了!后来发现,如果只有两个 1,仅需 $2^{k + 1} - 1$ 即可。然后多余的 1 可以 x 和 y 每个位置都有即可,但不要回到了 k + 1 位 后最后一位。然后注意边界判断!(一开始忘了特判 a = 0,然后 hack 的时候才发现,提交前复制变成剪切导致 WA 一次,吐了,不过最后此场还是加分了。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int a, b, k; std ::cin >> a >> b >> k; if (b == 1 ) { if (k == 0 ) { std ::cout << "Yes\n" ; std ::cout << "1" << std ::string (a, '0' ) << "\n" ; std ::cout << "1" << std ::string (a, '0' ) << "\n" ; } else { std ::cout << "No\n" ; } } else { if (a == 0 ) { if (k == 0 ) { std ::cout << "Yes\n" ; std ::cout << std ::string (b, '1' ) << "\n" ; std ::cout << std ::string (b, '1' ) << "\n" ; } else { std ::cout << "No\n" ; } } else if (k <= a + b - 2 ) { std ::cout << "Yes\n" ; std::string x(a + b, '0'), y(a + b, '0'); x[0 ] = y[0 ] = '1' ; x[a + b - k - 1 ] = '1' ; y.back() = '1' ; int t = b - 2 ; for (int i = 1 ; t && i < a + b; ++i) if (i != a + b - k - 1 ) { x[i] = y[i] = '1' ; --t; } std ::cout << x << "\n" ; std ::cout << y << "\n" ; } else { std ::cout << "No\n" ; } } return 0 ; }
给定 m 个 n 维 向量,求一个 n 维向量使得它和所有其它向量不同的值的个数不超过 k = 2(若没有输出 No)。
首先看 k = 2 的情况,我们可以以第一个向量为基准,看它和其它向量值不同的个数。若超过 4,那么显然不能,若全不超过 2,那么显然可以。所以需要考虑不同值为 3 或 4 的情况。无论何种情况,相同的部分必然是最终答案的部分(否则一但不同就有两个位置都不同,必然不行),因此仅仅需要枚举即可。那么一般的 k 呢?怎么写实现呢(得到纠错码模板)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;bool next (std ::vector <int > &a, int mx) { int n = a.size (), i = 1 ; while (i <= n && a[n - i] == mx - i) ++i; if (i > n) return false ; ++a[n - i]; for (int j = 1 ; j < i; ++j) { a[n - j] = a[n - i] + (i - j); } return true ; } class ECC { std ::vector <std ::vector <int >> a; int k; std ::vector <std ::vector <int >> bad; int n, m, mxId; void updateMxId (int i) { if (bad[i].size () > bad[mxId].size ()) mxId = i;} bool dfs (int c) { auto bd = bad[mxId]; if (bd.size () <= k) return true ; if (bd.size () - k > c) return false ; std ::vector <int > f (bd.size () - k) ; iota(f.begin (), f.end (), 0 ); int tMxId = mxId; do { mxId = tMxId; std ::queue <int > tmp; for (auto x : f) { tmp.push(r[bd[x]]); for (int i = 0 ; i < n; ++i) { if (a[i][bd[x]] == r[bd[x]]) { bad[i].emplace_back(bd[x]); } if (a[i][bd[x]] == a[mxId][bd[x]]) { bad[i].erase(std ::find (bad[i].begin (), bad[i].end (), bd[x])); } } r[bd[x]] = a[mxId][bd[x]]; } for (int i = 0 ; i < n; ++i) updateMxId(i); if (dfs(c - f.size ())) return true ; for (auto x : f) { for (int i = 0 ; i < n; ++i) { if (a[i][bd[x]] == r[bd[x]]) { bad[i].emplace_back(bd[x]); } if (a[i][bd[x]] == tmp.front()) { bad[i].erase(std ::find (bad[i].begin (), bad[i].end (), bd[x])); } } r[bd[x]] = tmp.front(); tmp.pop(); } } while (next(f, bd.size ())); return false ; } public : std ::vector <int > r; ECC(std ::vector <std ::vector <int >> _a) : a(_a), r(a[0 ]) { n = a.size (); m = r.size (); bad.resize(n); mxId = 0 ; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < m; ++j) if (a[i][j] != r[j]) { bad[i].emplace_back(j); } updateMxId(i); } } void setK (int _k) { k = _k;} bool solve () { return dfs(k);} }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std ::vector <std ::vector <int >> a (n, std ::vector <int >(m)) ; for (auto &x : a) for (auto &i : x) std ::cin >> i; ECC A (a) ; A.setK(2 ); if (A.solve()) { std ::cout << "Yes\n" ; for (auto x : A.r) std ::cout << x << " " ; std ::cout << "\n" ; } else { std ::cout << "No\n" ; } return 0 ; }
1486A :低级错误还一直没发现1 2 3 4 5 6 7 8 9 10 11 bool solve () { int n; std ::cin >> n; LL r = 0 ; for (int i = 0 , x; i < n; ++i) { std ::cin >> x; r += (x - i); if (r < 0 ) return 0 ; } return 1 ; }
这什么水平?数据还没读完你给我结束了?
1486C :交互题,每次询问区间第二大这个题,我一直在想是不是要用 三分法。我在想啥呢。没有充分利用第二大始终是全局第二大。你知道的区间范围和你查询的区间范围可以是不同的!意识到这一点简单二分就能做了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;std ::map <std ::pair<int , int >, int > mp;int cnt = 0 ;int query (int l, int r) { if (l == r) return --cnt; if (mp.count({l, r})) return mp[{l, r}]; std ::cout << "? " << l << " " << r << std ::endl ; int ans; std ::cin >> ans; return mp[{l, r}] = ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; int l = 1 , r = n, q = query(l, r); while (l < r) { int m = (l + r) / 2 ; if (q <= m) { if (l == m && q == l) { l = m + 1 ; continue ; } int qq = query(std ::min (q, l), m); if (qq == q) r = m; else l = m + 1 ; } else { if (m + 1 == r && q == r) { r = m; continue ; } int qq = query(m + 1 , std ::max (r, q)); if (qq == q) l = m + 1 ; else r = m; } } std ::cout << "! " << r << std ::endl ; return 0 ; }
如果区间长度固定,我们可以用树状数组,每次添加元素删除元素即可。但是区间长度不固定,仅仅是限制了区间长度不小于 k。没有什么好的办法,但是可以将答案进行二分,然后把所有元素变成 1 或 -1,然后如果有区间长度大于 k 且和为正,那就说明答案可取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <int > a (n + 1 ) ; for (int i = 1 ; i <= n; ++i) std ::cin >> a[i]; auto f = [&](int m) { std ::vector <int > b(n + 1 ), c(n + 1 ); for (int i = 1 ; i <= n; ++i) b[i] = (a[i] >= m ? 1 : -1 ); for (int i = 1 ; i <= n; ++i) b[i] += b[i - 1 ]; for (int i = 1 ; i <= n; ++i) { c[i] = std ::min (c[i - 1 ], b[i]); } for (int i = k; i <= n; ++i) if (b[i] > c[i - k]) return true ; return false ; }; int l = 1 , r = n; while (l <= r) { int m = (l + r) / 2 ; if (f(m)) l = m + 1 ; else r = m - 1 ; } std ::cout << r << "\n" ; return 0 ; }
如果这个问题一开始就在环上考虑就能秒做了,浪费自己太多时间了。
1487C :所有选手比赛得分一致首先注意到如果所有选手个数为 奇数,那么它们可以输赢对半;否则至少需要平一场。
1487E :经典包含冲突的选择,MEX 应用我一开始以为是网络流,可是复杂度不支持我这么想,然后我就想着贪心然后 DP,一开始以为复杂度不行,后来想想连边的条数不超过 1e5,那必然可以呀。基本上我们对每一个位置,想上一次合法的最小。把上一次进行排序,然后 mex 一下就可以了,哎写的太慢了好气啊!(为什么不老老实实的做掉这题,做掉了就能加分了!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const int INF = 5e8 ;std ::vector <int > solve (const std ::vector <int > &a, int m) { int n = a.size (); std::vector<int> id(n), ai(n); std ::iota(id.begin (), id.end (), 0 ); std ::sort(id.begin (), id.end (), [&](int i, int j) { return a[i] < a[j]; }); for (int i = 0 ; i < n; ++i) ai[id[i]] = i; int p; std ::vector <int > b (m) ; std ::vector <std ::set <int >> c (m) ; std ::cin >> p; for (int i = 0 , x, y; i < p; ++i) { std ::cin >> x >> y; --x; --y; c[y].insert(ai[x]); } auto mex = [&](const std ::set <int > &d) { for (int i = 0 ; i < n; ++i) if (!d.count(i)) return i; return n; }; for (int i = 0 ; i < m; ++i) { int j = mex(c[i]); b[i] = (j == n ? INF : a[id[j]]); } return b; }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n[4 ]; for (int i = 0 ; i < 4 ; ++i) std ::cin >> n[i]; std ::vector <int > a[4 ]; for (int i = 0 ; i < 4 ; ++i) a[i].resize(n[i]); for (int i = 0 ; i < 4 ; ++i) for (auto &x : a[i]) std ::cin >> x; for (int i = 1 ; i < 4 ; ++i) { auto t = solve(a[i - 1 ], n[i]); for (int j = 0 ; j < n[i]; ++j) a[i][j] += t[j]; } int mx = *std ::min_element(a[3 ].begin (), a[3 ].end ()); std ::cout << (mx < INF ? mx : -1 ) << "\n" ; return 0 ; }
官方题解 给了一个更好的做法:每一个位置先删掉冲突点,再加上这些冲突点,注意可能有重复,因此要用 multiset。
给定 $c_1$ 个 a,.., $c_26$ 个 z,问你有多少个满足长度为 n 且任意子串都不是奇数长的回文的字符串。
如果没有回文的限制,那么答案自然就是 $\prod_{i = 1}^{26} \sum_{j = 0}^{c_i} \frac{x^j}{j!}$ 在 $x^n$ 下的系数。
设 dp[i][j]
表示长度为 i 和为 j 的 hybrid 的个数。状态转移 dp[i][j] -> dp[i + 1][j + b[i]]
以及 dp[i][j] -> dp[i + 1][b[i]]
。注意到 如果 $j = 0$ 就重复了。
我们利用水涨船高技术。每次操作 key 值都加 b[i]
那么转移 1 相当于没搞,转移 2 即使添加 key 为 0 的值,当前所有数的和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const LL M = 1e9 + 7 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::map <LL, LL> mp{{0 , 1 }}; LL key = 0 , sm = 1 ; for (int i = 0 , x; i < n; ++i) { std ::cin >> x; LL a = mp[key]; mp[key] = sm; key -= x; sm = (sm * 2 - a + M) % M; } std ::cout << sm << "\n" ; } return 0 ; }
给一个每个叶子节点深度都为 d 的有根树,从根开始有红蓝两个硬币,每轮
红色硬币选择到达某一个儿子节点
蓝色硬币选择移动到任何一个深度比自己大 1 的节点。
红蓝硬币可以互换位置
每一轮它们都能得到 $|a_r - a_b|$ 分,问最终移动到叶子节点时最高得分。
我们设 dp[i]
:此轮结束红色节点在 i 节点位置,当前的最大得分。我们考虑状态转移
若此轮第二步结束时节点 i 上是红色硬币,那么 $dp[i] = dp[fa[i]] + \max_{dist[j] = dist[i]} |a_i - a_j|$(显然我们选择最小或者最大的 $a_j$)。反之我们需要最大化 $dp[fa[j]] + |a_j - a_i|$,因为我们需要最大化 $dp[fa[j]] + a_j$ 和 $dp[fa[j]] - a_j$。所以最终我们有一个 $O(n)$ 的算法。
矩阵中原始元素小于等于 16,新元素需要时原来元素的倍数,且使得相邻元素的绝对值是一个非 0 四次方数。取一下 lcm 可以使得所有元素差值都为 0,然后这个四次方数可以取原来数的四次方(分奇偶)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; const int lcm = 720720 ; for (int i = 0 ; i < n; ++i) { for (int j = 0 , x; j < m; ++j) { std ::cin >> x; if ((i + j) & 1 ) { std ::cout << lcm + x * x * x * x << "\n" ; } else std ::cout << lcm << "\n" ; } } return 0 ; }
题意:问满足 $\lfloor \frac{a}{b} \rfloor = a \mod b, 1 \leq a \leq x, 1 \leq b \leq y$ 的 $(a, b)$ 有多少对。
做法:上式等价于 $a = (\lfloor \frac{a}{b} \rfloor)(b + 1)$。所以 $a = (b + 1) n$ 且 $1 \leq n < b$。我们固定 b,则答案就是
然后整除分块即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { LL x, y; std ::cin >> x >> y; LL r = 0 , b = 3 ; ++y; while (b <= y && b * (b - 2 ) <= x) { r += b - 2 ; ++b; } y = std ::min (x, y); for (LL j; b <= y; b = j + 1 ) { j = std ::min (y, x / (x / b)); r += (x / b) * (j - b + 1 ); } std ::cout << r << "\n" ; } return 0 ; }
1480C :交互题,找一个排列中的极小值点首先让左边相邻两个降序,右边相邻两个升序(否则直接结束了),然后二分比较 m 与 m + 1 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int solve () { int n; std ::cin >> n; std ::vector <int > a (n + 1 ) ; auto query = [&](int x) { if (a[x]) return a[x]; std ::cout << "? " << x << std ::endl ; int r; std ::cin >> r; return r; }; a[1 ] = query(1 ); if (a[1 ] == 1 ) return 1 ; a[2 ] = query(2 ); if (a[2 ] > a[1 ]) return 1 ; a[n] = query(n); if (a[n] == 1 ) return n; a[n - 1 ] = query(n - 1 ); if (a[n - 1 ] > a[n]) return n; int l = 2 , r = n - 1 ; while (l < r) { int m = (l + r) / 2 ; a[m] = query(m); a[m + 1 ] = query(m + 1 ); if (a[m] > a[m + 1 ]) { l = m + 1 ; } else { r = m; } } return r; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int r = solve(); std ::cout << "! " << r << std ::endl ; return 0 ; }
题意:每次从数列 a 中取出一个数放在尾部,使得相同的数字挨在一起,问最少需要的次数。
做法:首先反过来考虑,看最多能有多少数维持不动(一下子简单了不少,如果直接处理原问题,会要考虑移动顺序就很难考虑了)我们显然是从后往前好考虑一些,我们设 dp[i]
为 $[i, n]$ 中最多维持不动的数。我们考虑状态转移,我们可以删除第 i 个数,此时 dp[i] = dp[i + 1]
,否则我们保留 $[i, n]$ 中所有值为 a[i]
的数,如果更进一步 $[1, i - 1]$ 中不再出现 a[i]
,那么我们可以把最后一个出现 a[i]
后面的也拿到,反之不能拿到的原因是,前面的 a[i]
要删掉要放在后面,因此,最后一个 a[i]
后面什么都不能放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std::vector<int> a(n + 1), l(n + 1), r(n + 1), cnt(n + 1), dp(n + 2); for (int i = 1 ; i <= n; ++i) { std ::cin >> a[i]; r[a[i]] = i; } for (int i = n; i > 0 ; --i) l[a[i]] = i; for (int i = n; i > 0 ; --i) { ++cnt[a[i]]; if (l[a[i]] == i) { dp[i] = std ::max (dp[i + 1 ], cnt[a[i]] + dp[r[a[i]] + 1 ]); } else { dp[i] = std ::max (dp[i + 1 ], cnt[a[i]]); } } std ::cout << n - dp[1 ] << "\n" ; return 0 ; }
题意:是否能过通过一次调整(删一条边,加一条边)使得删除该点后的最大连通分支节点个数不超过 $\frac{n}{2}$
做法:首先考虑节点 1 为根,如果所有与 1 相连的子树的节点数均不超过 $\frac{n}{2}$,那么不用操作,已经可以作为重心了,否则最多只有一个子树的节点数大于 $\frac{n}{2}$,那么我们需要在这个子树中找一个节点个数不超过 $\frac{n}{2}$ 的子树,把它删了然后剩下的节点个数还要不超过 $\frac{n}{2}$(自然删节点最多的那个)。所以需要预处理出这个值。然后需要考虑换根(状态转移)。现在假设 fa 的结果已经搞定了,我们要看它的儿子节点 u,如果 u 的所有儿子的节点数都小于 $\frac{n}{2}$,那么我们要看 sz[1] - sz[u]
是否也小于 $\frac{n}{2}$,如果是不用操作了,否则,我们就要看抛弃以 v 为节点的子树后,整棵树节点不超过 $\frac{n}{2}$ 的最大子树(为此,我们需要预处理最大子儿子和次大子儿子)。如果 u 有个儿子节点数大于 $\frac{n}{2}$,那么和 1 一样判断即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <std ::vector <int >> e (n + 1 ) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } std::vector<int> sz(n + 1), mx(n + 1), mmx(n + 1), subtree(n + 1); std ::function<void (int , int )> pdfs = [&](int u, int fa) -> void { sz[u] = 1 ; for (auto v : e[u]) if (v != fa) { pdfs(v, u); sz[u] += sz[v]; subtree[u] = std ::max (subtree[u], subtree[v]); if (subtree[v] > mx[u]) { mmx[u] = mx[u]; mx[u] = subtree[v]; } else if (subtree[v] > mmx[u]) { mmx[u] = subtree[v]; } } if (sz[u] <= n / 2 ) subtree[u] = sz[u]; }; pdfs(1 , 0 ); std::vector<int> mxsub(n + 1), ans(n + 1); std ::function<void (int , int )> dfs = [&](int u, int fa) -> void { ans[u] = 1 ; mxsub[u] = mxsub[fa]; if (subtree[u] == mx[fa]) { mxsub[u] = std ::max (mxsub[fa], mmx[fa]); } else { mxsub[u] = std ::max (mxsub[fa], mx[fa]); } if (sz[1 ] - sz[u] <= n / 2 ) mxsub[u] = std ::max (mxsub[u], sz[1 ] - sz[u]); if (sz[u] <= n / 2 ) { if (sz[1 ] - sz[u] - mxsub[u] > n / 2 ) ans[u] = 0 ; } else { for (auto v : e[u]) if (v != fa && sz[v] > n / 2 ) { if (sz[v] - subtree[v] > n / 2 ) ans[u] = 0 ; } } for (auto v : e[u]) if (v != fa) dfs(v, u); }; dfs(1 , 0 ); for (int i = 1 ; i <= n; ++i) std ::cout << ans[i] << " \n" [i == n]; return 0 ; }
题意:以树的某个节点为根的子树的白色节点减去黑色节点的个数的最大值
做法:首先以 1 为根预处理所有子树的答案,然后分情况状态转移。具体看子节点是否对父节点做了贡献。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n + 1 ) ; for (int i = 1 ; i <= n; ++i) { std ::cin >> a[i]; if (a[i] == 0 ) a[i] = -1 ; } std ::vector <std ::vector <int >> e (n + 1 ) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } std ::vector <int > ans (n + 1 ) ; std ::function<void (int , int )> pdfs = [&](int u, int fa) -> void { ans[u] = a[u]; for (auto v : e[u]) if (v != fa) { pdfs(v, u); if (ans[v] > 0 ) ans[u] += ans[v]; } }; pdfs(1 , 0 ); std ::function<void (int , int )> dfs = [&](int u, int fa) -> void { for (auto v : e[u]) if (v != fa) { if (ans[v] < 0 ) { if (ans[u] > 0 ) ans[v] += ans[u]; } else { ans[v] = std ::max (ans[v], ans[u]); } dfs(v, u); } }; dfs(1 , 0 ); for (int i = 1 ; i <= n; ++i) std ::cout << ans[i] << " \n" [i == n]; return 0 ; }
题意:$n + 1$ 个点排成一排,中间有 n 跳有向边,问从 i 点出发最多可以经过多少个点(没走一次,所有边都会反向一次)
做法:首先,如果不反向,显然可以从左到右,从右到左跑两次得到结果。现在反向了,我们可以把奇数边反向,或者把偶数边反向,两个都需要,用数组好标号。注意逻辑要清晰了,怎么存都想清楚了,列好式子再写代码就会很优雅。
下面代码写的相当优雅! 看了 WZYYN 的代码 发现他比我这代码优雅多了。。直接 f[0], g[0]
表示往前往后没改变时的答案,f[1], g[1]
表示改变了的答案。然后递推就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;std::pair<std::vector<int>, std::vector<int>> f(std::string s) { int n = s.size (); std::vector<int> a(n + 1, 0), b(n + 1, 0); for (int i = 0 ; i < n; ++i) { if (s[i] == 'L' ) a[i + 1 ] = a[i] + 1 ; } for (int i = n - 1 ; i >= 0 ; --i) { if (s[i] == 'R' ) b[i] = b[i + 1 ] + 1 ; } return {a, b}; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::string s; std ::cin >> n >> s; std ::string ss[2 ]; ss[0 ] = ss[1 ] = s; for (int i = 1 ; i < n; i += 2 ) ss[0 ][i] = (ss[0 ][i] == 'L' ? 'R' : 'L' ); for (int i = 0 ; i < n; i += 2 ) ss[1 ][i] = (ss[1 ][i] == 'L' ? 'R' : 'L' ); std ::vector <int > a[2 ], b[2 ]; std ::tie(a[0 ], b[0 ]) = f(ss[0 ]); std ::tie(a[1 ], b[1 ]) = f(ss[1 ]); std ::vector <int > c (n + 1 , 1 ) ; for (int i = 0 ; i < n; ++i) { if (s[i] == 'L' ) { c[i + 1 ] += a[i & 1 ][i + 1 ]; } else { c[i] += b[i & 1 ][i]; } } for (int i = 0 ; i <= n; ++i) std ::cout << c[i] << " \n" [i == n]; } return 0 ; }
首先注意到最终结果必然是形式为 $r[i] = c_0 A[i] + \cdots c_{n - 1 - i} A[n - 1]$ 的样子,并且这些 $c_i$ 都只和 m, L
有关,实际上它们的系数不难看出是 $(1 + x + \cdot x^{L- 1})^m$ 的系数,并且我们计算的时候只要模 $x^n$ 即可。注意到上式可以直接生成函数开方直接求解,复杂度为 $O(n \log n \log m)$,当然了注意到 $(1 + x + \cdot x^{L- 1})^m = (1 - x^L)^m (1 - x)^{-m}$,直接二项式展开求一次乘法,所以可以做到整体复杂度为 $O(n \log n)$。我也是这个题的最佳解答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const LL M = 998244353 , ROOT = 3 ;LL powMod (LL x, int n) { LL r (1 ) ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } void bitreverse (std ::vector <LL> &a) { for (int i = 0 , j = 0 ; i != a.size (); ++i) { if (i > j) std ::swap(a[i], a[j]); for (int l = a.size () >> 1 ; (j ^= l) < l; l >>= 1 ); } } void ntt (std ::vector <LL> &a, bool isInverse = false ) { LL g = powMod(ROOT, (M - 1 ) / a.size ()); if (isInverse) { g = powMod(g, M - 2 ); LL invLen = powMod(LL(a.size ()), M - 2 ); for (auto & x: a) x = x * invLen % M; } bitreverse(a); std ::vector <LL> w (a.size (), 1 ) ; for (int i = 1 ; i != w.size (); ++i) w[i] = w[i - 1 ] * g % M; auto addMod = [](LL x, LL y) { return (x += y) >= M ? x -= M : x; }; for (int step = 2 , half = 1 ; half != a.size (); step <<= 1 , half <<= 1 ) { for (int i = 0 , wstep = a.size () / step ; i != a.size (); i += step ) { for (int j = i; j != i + half; ++j) { LL t = (a[j + half] * w[wstep * (j - i)]) % M; a[j + half] = addMod(a[j], M - t); a[j] = addMod(a[j], t); } } } } void mul (std ::vector <LL>& a, std ::vector <LL> b) { int sz = 1 , tot = a.size () + b.size () - 1 ; while (sz < tot) sz *= 2 ; a.resize(sz); b.resize(sz); ntt(a); ntt(b); for (int i = 0 ; i != sz; ++i) a[i] = a[i] * b[i] % M; ntt(a, 1 ); a.resize(tot); } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); const int N = 1e5 + 2 ; std ::vector <LL> inv (N) ; inv[1 ] = 1 ; for (int i = 2 ; i < N; ++i) inv[i] = (M - M / i) * inv[M % i] % M; int cas = 1 ; std ::cin >> cas; for (int T = 1 ; T <= cas; ++T) { std ::cout << "Case " << T << ": " ; int n, L, m; std ::cin >> n >> L >> m; std ::vector <LL> a (n) ; for (auto &x : a) std ::cin >> x; std ::reverse(a.begin (), a.end ()); std ::vector <LL> b (n) ; LL now = 1 ; for (int i = 0 ; i < n; ++i) { b[i] = now; now = now * (m + i) % M * inv[i + 1 ] % M; } mul(a, b); a.resize(n); std ::fill (b.begin (), b.end (), 0 ); now = 1 ; for (int i = 0 ; i * L < n; ++i) { b[i * L] = (now + M) % M; now = -now * (m - i) % M * inv[i + 1 ] % M; } mul(a, b); a.resize(n); std ::reverse(a.begin (), a.end ()); for (int i = 0 ; i < n; ++i) std ::cout << a[i] << " " ; std ::cout << "\n" ; } return 0 ; }
题意:把两个数组a, b
合并成一个数组c
保持元素原有的顺序,使得 $\sum_{i = 1}^{n + m} c_i$ 最小。
首先观察到两个事实:1. 最终 c 数组中如果有相邻元素分别在不同的原数组,那么在前面的必然更大。2. 由于我们想尽量让大的元素放在前面,但是又要保持元素原有的性质,这样就会导致大的元素推自己前面的元素跑,例如 n 个 a 中元素,和 m 个 b 中元素,一个放前面一个放后面,那么哪一个放前面呢,计算之后会发现,平均值大的放前面即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;std::vector<std::pair<int, int>> f(std::vector<LL> &a, std::vector<LL> &s) { int n = a.size (); std ::stack <int > Q; Q.push(0 ); for (int i = 1 ; i < n; ++i) { if (s[i] - s[Q.top()] <= a[i] * (i - Q.top())) { int t = Q.top(); Q.pop(); while (!Q.empty() && (s[t] - s[Q.top()]) * (i + 1 - t) <= (s[i + 1 ] - s[t]) * (t - Q.top())) { t = Q.top(); Q.pop(); } Q.push(t); } else Q.push(i); } std ::vector <std ::pair<int , int >> r; int now = n; while (!Q.empty()) { r.emplace_back(Q.top(), now); now = Q.top(); Q.pop(); } return r; } LL solve () { int n, m; std ::cin >> n >> m; std::vector<LL> a(n), b(m); for (auto &x : a) std ::cin >> x; for (auto &x : b) std ::cin >> x; std::vector<LL> sa(n + 1), sb(m + 1); for (int i = 0 ; i < n; ++i) sa[i + 1 ] = sa[i] + a[i]; for (int i = 0 ; i < m; ++i) sb[i + 1 ] = sb[i] + b[i]; auto fa = f(a, sa), fb = f(b, sb); std ::vector <LL> c; while (!fa.empty() && !fb.empty()) { auto [la, ra] = fa.back(); auto [lb, rb] = fb.back(); if ((sa[ra] - sa[la]) * (rb - lb) >= (sb[rb] - sb[lb]) * (ra - la)) { fa.pop_back(); for (int i = la; i < ra; ++i) c.emplace_back(a[i]); } else { fb.pop_back(); for (int i = lb; i < rb; ++i) c.emplace_back(b[i]); } } if (fa.empty()) { for (int i = fb.back().first; i < m; ++i) c.emplace_back(b[i]); } else { for (int i = fa.back().first; i < n; ++i) c.emplace_back(a[i]); } LL ans = 0 ; for (int i = 0 ; i < n + m; ++i) ans += c[i] * (i + 1 ); return ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; for (int T = 1 ; T <= cas; ++T) { std ::cout << "Case " << T << ": " << solve() << "\n" ; } return 0 ; }
1478D :GCD once more此题,一眼看出系数之和恒定为 1,但是我一直想根据 k 的奇偶性递推,但是复杂度完全无法预测,一直卡着自己被卡炸了! 通过 2x - y
这样一直搞,那么最终只要满足 系数之和为 1,都会出现。所以这就是个 gcd 问题啊
有整数解,当且仅当
有解。当且仅当 $\gcd(a_1 - a_i, \cdots, a_n - a_i) | k - a_i$
我们可以取 $a_i$ 为最小值,这样求 gcd 就不会出现负数了。代码太简单就不写了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;class Dinic { int n; std ::vector <std ::pair<int , int >> e; std ::vector <std ::vector <int >> g; std ::vector <int > cur, h; bool bfs (int s, int t) { h.assign(n, -1 ); std ::queue <int > Q; h[s] = 0 ; Q.push(s); while (!Q.empty()) { int u = Q.front(); Q.pop(); for (auto i : g[u]) { auto [v, c] = e[i]; if (c > 0 && h[v] == -1 ) { h[v] = h[u] + 1 ; Q.push(v); } } } return h[t] != -1 ; } int dfs (int u, int t, int f) { if (u == t || f == 0 ) return f; int r = f; for (int &i = cur[u]; i < g[u].size (); ++i) { int j = g[u][i]; auto [v, c] = e[j]; if (c > 0 && h[v] == h[u] + 1 ) { int a = dfs(v, t, std ::min (r, int (c))); e[j].second -= a; e[j ^ 1 ].second += a; r -= a; if (r == 0 ) return f; } } return f - r; } public : Dinic(int _n) : n(_n), g(n) {} void addEdge (int u, int v, int c) { if (u == v) return ; g[u].emplace_back(e.size ()); e.emplace_back(v, c); g[v].emplace_back(e.size ()); e.emplace_back(u, 0 ); } int maxFlow (int s, int t) { int r = 0 ; while (bfs(s, t)) { cur.assign(n, 0 ); r += dfs(s, t, INT_MAX); } return r; } }; bool solve () { int nn, m, ss; std ::cin >> nn >> m >> ss; int n = 1 ; for (int i = 0 ; i < nn; ++i) n *= 10 ; Dinic g (n + 2 ) ; int s = n, t = n + 1 ; std ::vector <int > ban (n) ; for (int i = 0 , x; i < m; ++i) { std ::cin >> x; ban[x] = 1 ; } auto cal = [](int x) { int r = 0 ; while (x) { r += x % 10 ; x /= 10 ; } return r; }; int cs = cal(ss); auto add = [&](int i, int t) { if (t % 2 != cs % 2 ) return ; for (int j = 1 ; j < n; j *= 10 ) { if (i + j < n && cal(i + j) == t + 1 ) { if (!ban[i + j]) g.addEdge(i, i + j, 1 ); } else { if (!ban[i - 9 * j]) g.addEdge(i, i - 9 * j, 1 ); } if (i >= j && cal(i - j) == t - 1 ) { if (!ban[i - j]) g.addEdge(i, i - j, 1 ); } else { if (!ban[i + 9 * j]) g.addEdge(i, i + 9 * j, 1 ); } } }; for (int i = 0 ; i < n; ++i) if (!ban[i]) { if (cal(i) % 2 == cs % 2 ) g.addEdge(s, i, 1 ); else g.addEdge(i, t, 1 ); } ban[ss] = 1 ; for (int i = 0 ; i < n; ++i) if (!ban[i]) { add(i, cal(i)); } g.maxFlow(s, t); ban[ss] = 0 ; g.addEdge(s, ss, 1 ); add(ss, cs); return g.maxFlow(s, t); } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { std ::cout << (solve() ? "Alice\n" : "Bob\n" ); } return 0 ; }
考虑 $\gcd(x, y) = x ^ y$。我总想把最终形式给搞出来(想太多,吃力不讨好),首先显然 $x \neq y$(不考虑 0),不妨设 $x > y$,那么 $x - y \geq \gcd(x - y, y) = \gcd(x, y) = x^y \geq x - y$。从而知道 $\gcd(x, y) = x^y = x - y$,然后我还一直想继续推,甚至猜想 $x = 2^k(2n + 1), y = 2^{k + 1} \cdot n$,浪费了特别多的时间,其实此时显然可以枚举 gcd 的值在 $O(n \log n)$ 把所有情况找出来嘛。后面就是并查集常规操作了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const int N = 2e5 + 2 ;std ::vector <int > bad[N];void init () { for (int i = 1 ; i < N; ++i) { for (int j = i * 2 ; j < N; j += i) if ((j ^ i) == j - i){ bad[j - i].emplace_back(j); bad[j].emplace_back(j - i); } } for (int i = 1 ; i < N; ++i) std ::sort(bad[i].begin (), bad[i].end ()); } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); init(); int n, m; std ::cin >> n >> m; std::vector<int> p(n + m + 1), a(n + m + 1); std ::iota(p.begin (), p.end (), 0 ); std::vector<std::map<int, int>> mp(n + m + 1); for (int i = 1 ; i <= n; ++i) { std ::cin >> a[i]; mp[i].insert({a[i], 1 }); } std ::function<int (int )> find = [&](int x) ->int { if (p[x] != x) p[x] = find (p[x]); return p[x]; }; LL ans = 0 ; auto merge = [&](int x, int y) { int fx = find (x), fy = find (y); if (fx == fy) return ; if (mp[fx].size () < mp[fy].size ()) std ::swap(fx, fy); p[fy] = fx; for (auto [v, c] : mp[fy]) { for (auto t : bad[v]) if (mp[fx].count(t)) { ans += LL(mp[fx][t]) * c; } } for (auto [v, c] : mp[fy]) { mp[fx][v] += c; } }; auto change = [&](int x, int y) { int fx = find (x); auto &it = mp[fx]; for (auto t : bad[a[x]]) if (it.count(t)) { ans -= it[t]; } --it[a[x]]; for (auto t : bad[y]) if (it.count(t)) { ans += it[t]; } ++it[y]; a[x] = y; }; for (int i = 0 ; i < m; ++i) { int op, x, y; std ::cin >> op >> x >> y; if (op == 1 ) { a[x] = y; mp[x].insert({y, 1 }); } else if (op == 2 ) { merge(x, y); } else if (a[x] != y) { change(x, y); } std ::cout << ans << "\n" ; } return 0 ; }
首先最优答案肯定时先递增后递减的。相当于有一个制高点,枚举制高点,自然有 $O(n^2)$ 的算法。但是利用单调栈可以优化到 $O(n)$。设 l[i], r[i]
分别表示以 i 为最高点的(前,后)缀和最大值。只讨论前缀,那么求 l[i] 自然时往前找比它小的,然后继承比的小的答案了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; auto f = [&]() -> std ::vector <int > { std ::vector <int > pre(n, -1 ); std ::stack <int > Q; for (int i = 0 ; i < n; ++i) { while (!Q.empty() && a[Q.top()] >= a[i]) Q.pop(); if (!Q.empty()) pre[i] = Q.top(); Q.push(i); } return pre; }; auto g = [&]() -> std ::vector <int > { std ::vector <int > nxt(n, n); std ::stack <int > Q; for (int i = n - 1 ; i >= 0 ; --i) { while (!Q.empty() && a[Q.top()] >= a[i]) Q.pop(); if (!Q.empty()) nxt[i] = Q.top(); Q.push(i); } return nxt; }; auto pre = f(), suf = g(); std::vector<LL> b(n), c(n); for (int i = 0 ; i < n; ++i) { b[i] = LL(i - pre[i]) * a[i] + (pre[i] == -1 ? 0 : b[pre[i]]); } for (int i = n - 1 ; i >= 0 ; --i) { c[i] = LL(suf[i] - i) * a[i] + (suf[i] == n ? 0 : c[suf[i]]); } LL r = 0 ; int id = 0 ; for (int i = 0 ; i < n; ++i) { if (b[i] + c[i] - a[i] > r) { r = b[i] + c[i] - a[i]; id = i; } } for (int i = id; i != -1 ; i = pre[i]) { for (int j = i - 1 ; j != pre[i]; --j) a[j] = a[i]; } for (int i = id; i != n; i = suf[i]) { for (int j = i + 1 ; j != suf[i]; ++j) a[j] = a[i]; } for (int i = 0 ; i < n; ++i) std ::cout << a[i] << " \n" [i == n - 1 ]; return 0 ; }
LOJ P1823 :经典问题:问有多少对元素,它们之间没有比它们都大的元素用单调栈存可以被当前位置的人看到的人的编号,显然是单调不增的(你回头一看,看到的人升高时单调不减的)。由于有身高相同的情况,所以需要合并相同身高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 izlyforever 2021 /1 /26 23 :24 :58 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::stack <std ::pair<int , int >> S; LL r = 0 ; for (int i = 0 , x; i < n; ++i) { std ::cin >> x; int cnt = 0 ; while (!S.empty() && S.top().first <= x) { if (S.top().first == x) { cnt = S.top().second; } r += S.top().second; S.pop(); } if (!S.empty()) ++r; S.push({x, ++cnt}); } std ::cout << r << "\n" ; return 0 ; }
1009F :长链剖分 dsu on tree在 C++ 图论模板 长链剖分中有讲解。这个题重链剖分也可以写,但是要写成这样的(不需要编译器优化的版本):submission 105484073 ,更优雅更快(由于编译器优化)的写法:submission 105483361 会 RE。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;std ::vector <int > dsuOnTree (std ::vector <std ::vector <int >> &e, int rt = 1 ) { int n = e.size (); std::vector<int> sz(n), son(n), dep(n); dep[rt] = 0 ; std ::function<int (int , int )> pdfs = [&](int u, int fa) -> int { sz[u] = 1 ; for (auto v : e[u]) if (v != fa) { dep[v] = dep[u] + 1 ; sz[u] += pdfs(v, u); if (sz[v] > sz[son[u]]) son[u] = v; } return sz[u]; }; std ::vector <int > ans (n) ; std::vector<std::map<int, int>> mp(n); std ::function<void (int , int )> dfs = [&](int u, int fa) -> void { if (son[u] == 0 ) { ans[u] = dep[u]; mp[u].insert({dep[u], 1 }); return ; } dfs(son[u], u); std ::swap(mp[son[u]], mp[u]); ans[u] = ans[son[u]]; auto &mpu = mp[u]; int mx = mpu[ans[u]]; auto deal = [&](int x, int c) { auto &it = mpu[x]; it += c; if (it > mx || (it == mx && ans[u] >= x)) { ans[u] = x; mx = it; } }; deal(dep[u], 1 ); for (auto v : e[u]) if (v != fa && v != son[u]) { dfs(v, u); for (auto [t, x] : mp[v]) { deal(t, x); } } }; pdfs(rt,0 ); dfs(rt, 0 ); for (int i = 1 ; i < n; ++i) ans[i] -= dep[i]; return ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <std ::vector <int >> e (n + 1 ) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } auto r = dsuOnTree(e); for (int i = 1 ; i <= n; ++i) std ::cout << r[i] << "\n" ; return 0 ; } ``` C++ #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;#include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;std ::vector <int > dsuOnTree (std ::vector <std ::vector <int >> &e, int rt = 1 ) { int n = e.size (); std::vector<int> sz(n), son(n); std ::function<void (int , int )> pdfs = [&](int u, int fa) -> void { for (auto v : e[u]) if (v != fa) { pdfs(v, u); if (sz[v] > sz[son[u]]) son[u] = v; } sz[u] = sz[son[u]] + 1 ; }; std ::vector <int > ans (n) ; std ::function<std ::vector <int >(int , int )> dfs = [&](int u, int fa) -> std ::vector <int > { if (son[u] == 0 ) { ans[u] = 0 ; return {1 }; } auto a = dfs(son[u], u); ans[u] = ans[son[u]]; for (auto v : e[u]) if (v != fa && v != son[u]) { auto tmp = dfs(v, u); for (int ai = a.size () - 1 , ti = tmp.size () - 1 ; ti >= 0 ; --ti, --ai) { a[ai] += tmp[ti]; if (a[ai] > a[ans[u]] || (a[ai] == a[ans[u]] && ai > ans[u])) { ans[u] = ai; } } } a.emplace_back(1 ); if (a[ans[u]] == 1 ) ans[u] = sz[u] - 1 ; return a; }; pdfs(rt, 0 ); dfs(rt, 0 ); for (int i = 1 ; i < n; ++i) ans[i] = sz[i] - 1 - ans[i]; return ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <std ::vector <int >> e (n + 1 ) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } auto r = dsuOnTree(e); for (int i = 1 ; i <= n; ++i) std ::cout << r[i] << "\n" ; return 0 ; }
首先,我们可以枚举 lca(i, j),也就是说每一个节点都可以当其子树的 lca,它的两个子树中元素的 lca 必然是它。因此把这个答案算成是这个节点的答案。然后就是轻重链的问题了。注意到这里答案是异或值求和,那么我们可以诸位考虑即可。这里的写法跟 600E 的写法不一致。其实也可以写成一致的样子,600E 也可以写成我这个样子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;class Node {public : inline static const int N = 18 ; int a[N]{}; Node() {} Node(int x) { ++a[0 ]; int now = 0 ; while (x) { a[++now] = x & 1 ; x >>= 1 ; } } Node operator +=(const Node &A) { for (int i = 0 ; i < N; ++i) a[i] += A.a[i]; return *this ; } }; LL deal (const Node & A, const Node & B) { LL r = 0 ; for (int i = 1 ; i < Node::N; ++i) { r += (LL(A.a[0 ] - A.a[i]) * B.a[i] + LL(B.a[0 ] - B.a[i]) * A.a[i]) << (i - 1 ); } return r; } LL dsuOnTree (std ::vector <std ::vector <int >> &e, std ::vector <int > &a, int rt = 1 ) { int n = a.size (); std::vector<int> sz(n), son(n); std ::function<int (int , int )> pdfs = [&](int u, int fa) -> int { sz[u] = 1 ; for (auto v : e[u]) if (v != fa) { sz[u] += pdfs(v, u); if (sz[v] > sz[son[u]]) son[u] = v; } return sz[u]; }; std ::vector <LL> ans (n) ; std ::function<std ::unordered_map <int , Node>(int , int )> dfs = [&](int u, int fa) -> std ::unordered_map <int , Node> { if (son[u] == 0 ) return {{a[u], Node(u)}}; auto mp = dfs(son[u], u); mp[a[u]] += Node(u); LL r = 0 ; for (auto v : e[u]) if (v != fa && v != son[u]) { auto tmp = dfs(v, u); for (auto [i, x] : tmp) { if (auto it = mp.find (i ^ a[u]); it != mp.end ()) { r += deal(it->second, x); } } for (auto [i, x] : tmp) mp[i] += x; } ans[u] = r; return mp; }; pdfs(rt, rt); dfs(rt, rt); LL r = 0 ; for (int i = 1 ; i < n; ++i) r += ans[i]; return r; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n + 1 ) ; for (int i = 1 ; i <= n; ++i) std ::cin >> a[i]; std ::vector <std ::vector <int >> e (n + 1 ) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } std ::cout << dsuOnTree(e, a) << std ::endl ; return 0 ; }
600E :dsu on tree不借鉴别人,自己独创的优秀写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;std ::vector <LL> dsuOnTree (std ::vector <std ::vector <int >> &e, std ::vector <int > &a, int rt = 1 ) { int n = a.size (); std::vector<int> sz(n), son(n), cnt(n); std ::vector <LL> ans (n) ; std ::function<int (int , int )> pdfs = [&](int u, int fa) -> int { sz[u] = 1 ; for (auto v : e[u]) if (v != fa) { sz[u] += pdfs(v, u); if (sz[v] > sz[son[u]]) son[u] = v; } return sz[u]; }; int mx = 0 , Son = 0 ; LL sm = 0 ; std ::function<void (int , int )> deal = [&](int u, int fa) -> void { ++cnt[a[u]]; if (cnt[a[u]] > mx) { mx = cnt[a[u]]; sm = a[u]; } else if (cnt[a[u]] == mx) { sm += a[u]; } for (auto v : e[u]) if (v != fa && v != Son) { deal(v, u); } }; std ::function<void (int , int )> del = [&](int u, int fa) -> void { --cnt[a[u]]; for (auto v : e[u]) if (v != fa) del(v, u); }; std ::function<void (int , int , bool )> dfs = [&](int u, int fa, bool save) -> void { for (auto v : e[u]) if (v != fa && v != son[u]) { dfs(v, u, 0 ); } if (son[u]) dfs(son[u], u, 1 ); Son = son[u]; deal(u, fa); Son = 0 ; ans[u] = sm; if (!save) { del(u, fa); sm = 0 ; mx = 0 ; } }; pdfs(rt, rt); dfs(rt, rt, 1 ); return ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n + 1 ) ; for (int i = 1 ; i <= n; ++i) std ::cin >> a[i]; std ::vector <std ::vector <int >> e (n + 1 ) ; for (int i = 1 ; i < n; ++i) { int u, v; std ::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } auto r = dsuOnTree(e, a); for (int i = 1 ; i <= n; ++i) std ::cout << r[i] << " \n" [i == n]; return 0 ; }
首先 c = 0 时特判,$c \geq 1$ 时,观察到 $a_n = c^{bit}$, bit 为 n 的二进制中 1 的个数。然后我们只需看小于 10…0(k 个 0) 时的答案即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const LL M = 1e9 + 7 ;LL powMod (LL x, LL n) { LL r = 1 ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } const int N = 3e5 + 2 ;LL fac[N], ifac[N]; void init () { fac[0 ] = 1 ; for (int i = 1 ; i < N; ++i) fac[i] = fac[i - 1 ] * i % M; ifac[N - 1 ] = powMod(fac[N - 1 ], M - 2 ); for (int i = N - 1 ; i; --i) ifac[i - 1 ] = ifac[i] * i % M; } LL binom (int n, int k) { return fac[n] * ifac[k] % M * ifac[n - k] % M; } LL solve (int n, int a, int c) { LL r = 0 ; for (int i = 0 ; i <= n; ++i) { r += binom(n, i) * powMod(c, i + a) % M; } return r % M; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::string s; int c; std ::cin >> s >> c; if (c == 0 ) { std ::cout << "1\n" ; return 0 ; } init(); LL r = 0 ; int a = 0 ; for (int i = 0 ; i < s.size (); ++i) if (s[i] == '1' ) { r += solve(s.size () - i - 1 , a++, c); } r += powMod(c, a); std ::cout << r % M << std ::endl ; return 0 ; }
从 0 位置出发,走到大于等于 n 的位置结束,每次平均概率在 [1, m] 步中选择步长来走,有 k 个坑,走到坑就回到起点 0。问结束前步数的期望是多少,如果无法结束就输出 -1。
做法:首先如果有连续 m 个坑(很好判断),必然无法结束,否则可以结束,我们设 dp[i]
表示从 i 出发的答案。显然 $dp[i] = 0, i \geq n$,我们从后往前跑,显然有状态转移,如果 i 位置有坑,那么 $dp[i] = dp[0]$, 否则 $dp[i] = (dp[i + 1] + \cdots dp[i + m]) / m + 1$。这个后缀和,我们可以用个变量记录下来。因此 所有的 dp[i] 都是一个 $a + b dp[0]$ 的形式,然后到最后有 $dp[0] = a + b dp[0]$ 从而就求得了结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m, k; std ::cin >> n >> m >> k; std ::vector <int > a (k) ; for (auto &x: a) std ::cin >> x; for (int i = m - 1 ; i < k; ++i) if (a[i] - a[i - m + 1 ] == m - 1 ) { std ::cout << "-1\n" ; return 0 ; } std::vector<double> b(n + m), c(n + m); double sb = 0 , sc = 0 ; for (int i = n - 1 ; i >= 0 ; --i) { if (a.size () && i == a.back()) { b[i] = 0 ; c[i] = 1 ; a.pop_back(); } else { b[i] = sb / m + 1 ; c[i] = sc / m; } sb += b[i] - b[i + m]; sc += c[i] - c[i + m]; } std ::cout .precision(8 ); std ::cout << std ::fixed << b[0 ] / (1 - c[0 ]) << std ::endl ; return 0 ; }
长为 k 取值在 [1, n] 且满足 $a_i \mid a_{i + 1}$ 的序列有多少个。
我一开始以为跟 n 的素因子有关,后来发现没法直接推公式,然后发现是一个 dp 问题,令 dp[k][x]
表示长为 k 满足 $a_i \mid a_{i + 1}$ 且每项都是 x 的因子的序列个数。答案必然就是 $\sum_{x = 1}^n dp[k - 1][x]$。复杂度 $O(k n \log n)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const int M = 1e9 + 7 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <std ::vector <int >> dp (k, std ::vector <int >(n + 1 )) ; for (int i = 1 ; i <= n; ++i) dp[0 ][i] = 1 ; for (int i = 1 ; i < k; ++i) { for (int j = 1 ; j <= n; ++j) { for (int t = j; t <= n; t += j) { (dp[i][t] += dp[i - 1 ][j]) %= M; } } } int r = 0 ; for (int i = 1 ; i <= n; ++i) (r += dp[k - 1 ][i]) %= M; std ::cout << r << std ::endl ; return 0 ; }
题意:每次可以同时使得相连两个数减 1,问是否能使得数组变成全 0。这个问题相当简单,因此换成,能否最多互换相连两个的值,使得原问题成立。
做法:原问题做法就是从左到右依次跑,如果跑出负数就不行,跑到最后不是 0 也不行。然后一开始我想错了,吃了两次 WA 之后,发现互换相邻两个之后原问题成立的前提是,从左到右跑和从右到左跑都不会出现负数。因此就保存左右两边跑的结果,然后只需考虑 4 个数的时候是否对就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); auto check = [](std ::vector <int > a) { int now = 0 ; for (auto x : a) { now = x - now; if (now < 0 ) return false ; } return now == 0 ; }; int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; std ::vector <int > b (n + 1 , -1 ) ; b[0 ] = 0 ; for (int i = 0 ; i < n && b[i] >= 0 ; ++i) { b[i + 1 ] = a[i] - b[i]; } if (b[n] == 0 ) { std ::cout << "YES\n" ; continue ; } bool flag = false ; int now = 0 ; for (int i = n - 1 ; i > 0 && now >= 0 ; --i) { if (b[i - 1 ] >= 0 && check(std ::vector <int >({b[i - 1 ], a[i], a[i - 1 ], now}))) { flag = true ; break ; } now = a[i] - now; } std ::cout << (flag ? "YES\n" : "NO\n" ); } return 0 ; }
一开始思路不清晰就写代码,写着发现有问题,被卡了挺长时间,导致 D 题差最后 10 分钟没有 debug 出一个小错误。
做法:从最大的开始找,然后删除对应的节点即可。初始值 x 必然是 a 中最大值和另一个值的和。分析好问题后再选取合适的 STL,我用的是 map,很多人用的是 multiset
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); auto f = [](std ::map <int , int > mp, int x) { std ::vector <std ::pair<int , int >> r; while (!mp.empty()) { auto it = mp.rbegin(); int u = it->first; if (--mp[u] == 0 ) mp.erase(u); if (mp.find (x - u) == mp.end ()) return std ::vector <std ::pair<int , int >>(); r.emplace_back(x - u, u); if (--mp[x - u] == 0 ) mp.erase(x - u); x = u; } return r; }; int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::map <int , int > mp; for (int i = 0 , x; i < 2 * n; ++i) { std ::cin >> x; ++mp[x]; } int x; std ::vector <std ::pair<int , int >> r; int t = mp.rbegin()->first; for (auto it = mp.begin (); it != mp.end (); ++it) { x = t + it->first; r = f(mp, x); if (r.size ()) break ; } if (r.size ()) { std ::cout << "YES\n" ; std ::cout << x << "\n" ; for (auto [x, y] : r) std ::cout << x << " " << y << "\n" ; } else std ::cout << "NO\n" ; } return 0 ; }
1473F :经典最大流(最小割)问题题意:给定长为 n($1 \leq n \leq 3000$) 的数组 a($1 \leq a_i \leq 100$), b($-10^5 \leq b_i \leq 10^5$),求 $\displaystyle \max_{i \in S} b_i$,其中集合 $S$ 满足若 $i \in S$,则任意 $0 \leq j < i$, 若 $a_i \equiv 0 \mod a_j$,那么 $j$ 也在 $S$ 中。
做法:设 $s = n$ 为源点,$t = n + 1$ 为汇点,如果 $b_i > 0$(则称 i 为正点,否则为负点),那么我们从源点 $s$ 到 $i$ 建一个容量为 $b_i$ 的边,反之我们就从 $i$ 到 $s$ 建一个容量为 $-b_i$ 的边。如果 $j < i$ 满足 $a_i \equiv 0 \mod a_j$,那么从 $j$ 到 $i$ 建一个容量为无穷大的边(一个必要的优化,直接这样建图,边太多了,根据这个性质的传递性,我们不妨找最后一个值为 $a_j$ 的点和 $i$ 相连)。 我们不妨先把所有正点全部放在 $S$ 中,然后求最小割即可,注意到满足性质的边容量是无限大的,因此我们必然会将这样的两个节点放在一起!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;class Dinic { int n; std ::vector <std ::pair<int , int >> e; std ::vector <std ::vector <int >> g; std ::vector <int > cur, h; bool bfs (int s, int t) { h.assign(n, -1 ); std ::queue <int > Q; h[s] = 0 ; Q.push(s); while (!Q.empty()) { int u = Q.front(); Q.pop(); for (auto i : g[u]) { auto [v, c] = e[i]; if (c > 0 && h[v] == -1 ) { h[v] = h[u] + 1 ; if (v == t) return true ; Q.push(v); } } } return false ; } int dfs (int u, int t, int f) { if (u == t) return f; int r = f; for (int &i = cur[u]; i < g[u].size (); ++i) { int j = g[u][i]; auto [v, c] = e[j]; if (c > 0 && h[v] == h[u] + 1 ) { int a = dfs(v, t, std ::min (r, c)); e[j].second -= a; e[j ^ 1 ].second += a; r -= a; if (r == 0 ) return f; } } return f - r; } public : Dinic(int _n) : n(_n), g(_n) {} void addEdge (int u, int v, int c) { g[u].emplace_back(e.size ()); e.emplace_back(v, c); g[v].emplace_back(e.size ()); e.emplace_back(u, 0 ); } int maxFlow (int s, int t) { int r = 0 ; while (bfs(s, t)) { cur.assign(n, 0 ); r += dfs(s, t, INT_MAX); } return r; } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std::vector<int> a(n), b(n); for (auto &x : a) std ::cin >> x; for (auto &x : b) std ::cin >> x; std ::vector <int > last (101 , -1 ) ; Dinic g (n + 2 ) ; int r = 0 ; for (int i = 0 ; i < n; ++i) { if (b[i] > 0 ) { r += b[i]; g.addEdge(n, i, b[i]); } else { g.addEdge(i, n + 1 , -b[i]); } for (int j = 1 ; j <= a[i]; ++j) { if (a[i] % j == 0 && last[j] != -1 ) { g.addEdge(i, last[j], INT_MAX); } } last[a[i]] = i; } r -= g.maxFlow(n, n + 1 ); std ::cout << r << std ::endl ; return 0 ; }
前缀和的历史最大值和历史最小值是特别好求的。后缀和的呢,却不那么显然。考虑后缀的时候,我们实际上要考虑后缀对最后结果的贡献。比如最大值,我们需要看当前后缀是否大于 0,如果小于等于 0 就直接抛弃重新开始,否则就继续保存。最小值同理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n, m; std ::string s; std ::cin >> n >> m >> s; std ::vector <int > c (n + 2 ) ; std::vector<std::pair<int, int>> a(n + 2), b(n + 2); int now = 0 , mx = 0 , mn = 0 ; for (int i = 1 ; i <= n; ++i) { if (s[i - 1 ] == '+' ) { mx = std ::max (mx, ++now); } else { mn = std ::min (mn, --now); } a[i] = {mn, mx}; c[i] = now; } int np = 0 , nq = 0 ; for (int i = n; i > 0 ; --i) { if (s[i - 1 ] == '+' ) { ++np; nq = std ::min (++nq, 0 ); } else { --nq; np = std ::max (--np, 0 ); } b[i] = {nq, np}; } while (m--) { int l, r; std ::cin >> l >> r; std ::cout << std ::max (a[l - 1 ].second, c[l - 1 ] + b[r + 1 ].second) - std ::min (a[l - 1 ].first, c[l - 1 ] + b[r + 1 ].first) + 1 << std ::endl ; } } return 0 ; }
1473E :经典最短路,去掉一个最长路,加上一个最短路一条路径的权值定义为 $\sum w_{e_i} - \max e_i + \min e_i$,显然这等价于 $\min \sum (w_{e_i}) - w_{e_j} + w_{e_k}$。因此我们可以建图:比如原始边为 (u, v, w), 一个节点到了 4u 表示是原始的长度, 4u + 1 表示减去了某个边,4u + 2 表示加上了某条边,4u + 3 表示既加了也减了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using edge = std ::vector <std ::vector <std ::pair<int , int >>>;std ::vector <LL> Dijkstra (int s, const edge &e) { std ::priority_queue<std ::pair<LL, int >> h; std ::vector <LL> dist (e.size ()) ; std ::vector <int > vis (e.size ()) ; dist[s] = 0 ; h.push({0 , s}); while (!h.empty()) { auto [d, u] = h.top(); h.pop(); if (vis[u]) continue ; vis[u] = 1 ; dist[u] = -d; for (auto [v, w] : e[u]) h.emplace(d - w, v); } return dist; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; edge e (4 * n) ; for (int i = 0 ; i < m; ++i) { int u, v, w; std ::cin >> u >> v >> w; --u; --v; for (int t = 0 ; t < 2 ; ++t) { for (int j = 0 ; j < 4 ; ++j) e[4 * u + j].emplace_back(4 * v + j, w); e[4 * u].emplace_back(4 * v + 1 , 0 ); e[4 * u].emplace_back(4 * v + 2 , 2 * w); e[4 * u].emplace_back(4 * v + 3 , w); e[4 * u + 1 ].emplace_back(4 * v + 3 , 2 * w); e[4 * u + 2 ].emplace_back(4 * v + 3 , 0 ); std ::swap(u, v); } } auto dist = Dijkstra(0 , e); for (int i = 7 ; i < 4 * n; i += 4 ) std ::cout << dist[i] << " " ; std ::cout << "\n" ; return 0 ; }
也可以不建成上述图,按照原始图建图,操作的时候再也可以,本质上一致更节省空间,代码稍微复杂一点。
大致 有 $n$ 个盒子,每个盒子有两个数,从中取去一个数,问最多可以取多少个不同的数。
数字为节点,盒子中的两个数连边(注意可能有重边),那个连通分支是树,那么答案就是连通分支节点数减 1,否则就是连通分支节点数。(树的情况容易证明,非树的情况总可以删边,删成只有树再多一条边的情况,然后也容易证明)
教程
1467C :类似于把 a, b 变成 a - b 问题(代码自解释)题目中 3 个袋子可以换成 $m(m \geq 3)$ 个袋子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n[3 ]{}; std ::cin >> n[0 ] >> n[1 ] >> n[2 ]; LL r = 0 ; std ::vector <std ::vector <int >> a (3 ) ; std ::vector <int > c; for (int i = 0 ; i < 3 ; ++i) { for (int j = 0 , x; j < n[i]; ++j) { std ::cin >> x; a[i].emplace_back(x); r += x; } } std ::vector <int > b; for (int i = 0 ; i < 3 ; ++i) { b.emplace_back(*std ::min_element(a[i].begin (), a[i].end ())); } std ::sort(b.begin (), b.end ()); LL ans = r - b[0 ] * 2 - b[1 ] * 2 ; for (int i = 0 ; i < 3 ; ++i) { LL tmp = r; for (int j = 0 ; j < n[i]; ++j) tmp -= a[i][j] * 2 ; ans = std ::max (ans, tmp); } std ::cout << ans << std ::endl ; return 0 ; }
题意:我们称 $a, b$ 相邻,如果 $\lcm(a, b)/gcd(a, b)$ 是平方数,这当且仅当 $ab$ 是平方数。我们定义 $f(n)$ 为 $n$ 的素因子的积,那么 $a, b$ 相邻,当且仅当 $f(a) = f(b)$。这样就好了呀。由于数据范围在 $1e6$ 之间,所以可以预处理以下就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const int N = 1e6 + 10086 ; int sp[N], p[N], f[N];int spf () { int cnt = 1 ; p[1 ] = 2 ; for (int i = 2 ; i < N; i += 2 ) sp[i] = 2 ; for (int i = 1 ; i < N; i += 2 ) sp[i] = i; for (int i = 3 ; i < N; i += 2 ) { if (sp[i] == i) p[++cnt] = i; for (int j = 1 ; j <= cnt && p[j] <= sp[i] && i * p[j] < N; ++j) { sp[i * p[j]] = p[j]; } } f[1 ] = 1 ; for (int i = 2 ; i < N; ++i) { int pi = i / sp[i]; if (pi % sp[i] == 0 ) f[i] = f[pi / sp[i]]; else f[i] = f[pi] * sp[i]; } return cnt; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; spf(); while (cas--) { int n; std ::cin >> n; std ::map <int , int > mp; for (int i = 0 , x; i < n; ++i) { std ::cin >> x; ++mp[f[x]]; } int r0 = INT_MIN, now = mp[1 ]; for (auto [u, v] : mp) { r0 = std ::max (r0, v); if (u != 1 && v % 2 == 0 ) now += v; } int r1 = std ::max (r0, now); int q; std ::cin >> q; while (q--) { LL w; std ::cin >> w; std ::cout << (w == 0 ? r0 : r1) << "\n" ; } } return 0 ; }
上面做法还是太慢了!注意到 $f(n)$ 表示 $n$ 的最小 “无平方因子” 的因子。因此可以用平方数预处理,可用下面代码加速
1 2 3 4 5 6 void init () { for (int i = 1 ; i * i < N; ++i) { int ii = i * i, cur = 0 ; for (int j = ii; j < N; j += ii) f[j] = ++cur; } }
比赛时,读题没读懂要干嘛。
用 01 对连通图染色,使得每个 0 周围全是 1,每个 1 周围都存在一个 0. 按照题解的说法:随便选择一点置 0,然后把它周围全部染色为 1,然后找和 1 相邻的没被染色的其中任意一个染色为 0,然后把 0 周围全部染色为 1,一直继续下去。
但是上述做法代码可能写的比较别扭,因此我们可以用队列,先随便选择一点置 0,放进队列中。在队列中的被染色为 1 的是真 1,染色为 0 的表示它和某个染色为 1 的节点相连。出队���后染色为 1 的是真的 1。队首的被染色为 0,那就把和它相邻的没被染色的变成 1 放在队列中,队首的被染色为 1,那就把它相邻的全变成 0(不管有没有被染色过),若没被染色就丢进队列中。
注意到:被染色为 0 后,染色不会变化。出队列的 1 周围的染色全为 0,被染色为 0,必然是某个出了队列的 1 帮它染的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n, m; std ::cin >> n >> m; std ::vector <std ::vector <int >> e (n) ; for (int i = 0 , u, v; i < m; ++i) { std ::cin >> u >> v; --u; --v; e[u].emplace_back(v); e[v].emplace_back(u); } std ::vector <int > val (n, -1 ) ; val[0 ] = 0 ; std ::queue <int > Q; Q.push(0 ); while (!Q.empty()) { int u = Q.front(); Q.pop(); if (val[u] == 0 ) { for (auto v : e[u]) { if (val[v] == -1 ) { val[v] = 1 ; Q.push(v); } else val[v] = 1 ; } } else { for (auto v : e[u]) if (val[v] == -1 ) { val[v] = 0 ; Q.push(v); } } } if (*std ::min_element(val.begin (), val.end ()) == -1 ) { std ::cout << "NO\n" ; } else { std ::cout << "YES\n" ; std ::vector <int > r; for (int i = 0 ; i < n; ++i) { if (val[i] == 0 ) r.emplace_back(i); } std ::cout << r.size () << "\n" ; for (auto i : r) std ::cout << i + 1 << " " ; std ::cout << "\n" ; } } return 0 ; }
1466A :线段上 $n$ 个点,两两距离差值的所有可能个数$n^2$ 的算法是显然的。
利用 Bitset,也是 $n^2$ 的 dp,然后用 bitset 存储数据,就可以达到 $n^2/64$ 的复杂度啦。
如果距离的最大值区间为 $N$,那么 $\sum x^{a_i} \sum x^(N - a_i)$ 中非负系数个数就是答案(所以有 $O(N\log N)$ 的做法。
显然就是固定 $j$,每位每位的求和即可。
1466F :$\mathbb{Z}_2$ 上 $m$ 维向量线性无关组注意到题目中至多两个位置非零,因此就可以用 并查集(一般情况倒是不知道有啥好办法)。注意到官方题解,可以多加一个维度,使得有每次正好有两个位置非零。
题意:给定由 0, 1, ?
构成的字符串,将 ?
变成 0 或 1,使得 01 字符和 10 字符权值和最小。(其中,01 权值为 x, 10 权值为 y)
不妨假设 $x \leq y$, 否则将字符串反序(x, y 互换)
做法:首先不考虑 ?
,此时我们可以通过当前位置为 0(看前方 1 的个数),当前位置为 1(看前置 0 的个数)得到基础权值 W
,然后注意到 01 的权值低于 10 的权值,因此当 m 个 ?
替换成 p 个 1 和 m - p 个 0 时候,把 0,放在最前面会让权值最小,因此我们可以考虑前缀问号给 0,后缀问号为 1 给答案的贡献。然后再加上基础权值 W 再加上 m(m-p)x
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::string a; LL x, y; std ::cin >> a >> x >> y; if (x > y) { std ::swap(x, y); std ::reverse(a.begin (), a.end ()); } int n = a.size (); std ::vector <int > p[2 ], s[2 ]; p[0 ].resize(n + 1 ); p[1 ].resize(n + 1 ); s[0 ].resize(n + 1 ); s[1 ].resize(n + 1 ); for (int i = 0 ; i < n; ++i) { p[0 ][i + 1 ] = p[0 ][i]; p[1 ][i + 1 ] = p[1 ][i]; if (a[i] != '?' ) ++p[a[i] - '0' ][i + 1 ]; } for (int i = n - 1 ; i >= 0 ; --i) { s[0 ][i] = s[0 ][i + 1 ]; s[1 ][i] = s[1 ][i + 1 ]; if (a[i] != '?' ) ++s[a[i] - '0' ][i]; } LL ord = 0 ; for (int i = 0 ; i < n; ++i) { if (a[i] == '0' ) ord += p[1 ][i] * y; if (a[i] == '1' ) ord += p[0 ][i] * x; } std::vector<LL> pre(n + 1), suf(n + 1); for (int i = 0 ; i < n; ++i) { pre[i + 1 ] = pre[i]; if (a[i] == '?' ) pre[i + 1 ] += s[1 ][i] * x + p[1 ][i] * y; } for (int i = n - 1 ; i >= 0 ; --i) { suf[i] = suf[i + 1 ]; if (a[i] == '?' ) suf[i] += s[0 ][i] * y + p[0 ][i] * x; } int cnt = std ::count(a.begin (), a.end (), '?' ), tnc = 0 ; LL r = ord + suf[0 ]; for (int i = 0 ; i < n; ++i) if (a[i] == '?' ) { ++tnc; r = std ::min (r, ord + pre[i + 1 ] + suf[i + 1 ] + x * (cnt - tnc) * tnc); } std ::cout << r << std ::endl ; return 0 ; }
官方题解 清晰明了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; LL s; std ::string a; std ::cin >> n >> s >> a; s -= 1 << (a.back() - 'a' ); a.pop_back(); s += 1 << (a.back() - 'a' ); a.pop_back(); s = abs (s); LL cnt[26 ]{}; for (auto x : a) ++cnt[x - 'a' ]; for (int i = 25 ; i >= 0 ; --i) { if ((s >> i) >= cnt[i]) s -= cnt[i] << i; else { int t = cnt[i] - (s >> i); s -= (s >> i) << i; if (t % 2 ) s = (1 << i) - s; } } std ::cout << (s == 0 ? "Yes" : "No" ) << std ::endl ; return 0 ; }
用桶排序和取正处理,直接起飞,复杂度骤降为 $O(n)$。
官方题解 属实精彩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const LL M = 998244353 ;LL powMod (LL x, LL n) { LL r = 1 ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::string s; std ::cin >> n >> m >> s; std::vector<LL> fac(n + 1), ifac(n + 1); fac[0 ] = 1 ; for (int i = 1 ; i <= n; ++i) fac[i] = fac[i - 1 ] * i % M; ifac[n] = powMod(fac[n], M - 2 ); for (int i = n; i >= 1 ; --i) ifac[i - 1 ] = ifac[i] * i % M; auto binom = [&](int n, int k) { return fac[n] * ifac[n - k] % M * ifac[k] % M; }; int B[2 ] = {}, res[2 ] = {}; for (int i = 0 ; i < n; ++i) { if (s[i] == '?' ) ++res[i % 2 ]; else if (s[i] == 'b' ) ++B[i % 2 ]; } int F = res[0 ] + res[1 ], x = B[1 ] + res[1 ] - B[0 ]; LL r = 0 ; for (int i = (x + n) % 2 ; i <= F; i += 2 ) { (r += abs (x - i) * binom(F, i)) %= M; } r = r * powMod(2L L, F * (M - 2 ) % (M - 1 )) % M; std ::cout << r << std ::endl ; return 0 ; }
这么简答的概率题,我竟然做了一个小时,被弱智的错误理解题意搞的头皮发麻!
题意:对一个长度为 $n$ 的 0-1 序列(首位为 1),0
表示不存档 1
表示存档,如果我们在第 i
关打赢了,那么我们进入第 i + 1
关,否则我们回到最近的一次存档处。那么战斗次数的期望就确定的。现在问题是期望值 $k$,能否给一个 $0-1$ 序列。
首先如果 $n = 1$,那么此时期望为 $\displaystyle \sum_{i = 1}^n \frac{i}{2^i} = 2$. 注意到如果 a[i] = 1
,一旦到达 i
位置,那么期望与前面的部分就无关了。所以其实我们只需考虑序列 10...0
。记期望为 $p_n$。那么
化简可得 $p_n = 2 (2^n - 1)$. 因此只要 k 是 2 的倍数,必然序列存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { LL n; std ::cin >> n; if (n & 1 ) { std ::cout << -1 << std ::endl ; continue ; } n /= 2 ; std ::vector <int > a; while (n) { int x = 1 ; while ((1L L << x) <= n + 1 ) ++x; --x; n -= (1L L << x) - 1 ; a.emplace_back(1 ); for (int i = 1 ; i < x; ++i) a.emplace_back(0 ); } std ::cout << a.size () << std ::endl ; for (auto x : a) std ::cout << x << " " ; std ::cout << std ::endl ; } return 0 ; }
题意:第 $k$ 次移动时,可以往前移动 $k$ 个位置,或往后移动一个位置,从 0 到 $n$ 最少多少步完成。
一开始在想用 bfs 或者 dfs 或者 dp 来做,没有第一时间搞贪心。写完 bfs 之后,跑不动吐了。此题最多向后走一个位置即可。找到最小的 x 满足 $1 + \cdots x \geq n$,如果 $1 + \cdots x = n + 1$,那么答案就是 $x + 1$,否则为 $x$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const int N = 1e6 + 2 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int x; std ::cin >> x; int r = 1 ; while (r * (r + 1 ) / 2 < x) ++ r; if (r * (r + 1 ) / 2 == x + 1 ) ++r; std ::cout << r << std ::endl ; } return 0 ; }
给定四个不同的点,将它们分别移动,使得称为一个正方形,且边分别和轴平行。左上角,右上角,左下角,右下角四个点明确之后,那么坐标分开讨论。利用一个事实,就是线段上,距离端点长度之和为常量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n = 4 ; std ::vector <pll> a (n) ; for (auto &[x, y] : a) std ::cin >> x >> y; LL ans = INT64_MAX; std ::sort(a.begin (), a.end ()); do { LL sum = 0 ; sum += abs (a[0 ].first - a[1 ].first); sum += abs (a[2 ].first - a[3 ].first); sum += abs (a[0 ].second - a[3 ].second); sum += abs (a[1 ].second - a[2 ].second); LL xr = std ::max (a[3 ].first, a[2 ].first) - std ::min (a[1 ].first, a[0 ].first); LL xl = std ::min (a[3 ].first, a[2 ].first) - std ::max (a[1 ].first, a[0 ].first); LL yr = std ::max (a[0 ].second, a[3 ].second) - std ::min (a[1 ].second, a[2 ].second); LL yl = std ::min (a[0 ].second, a[3 ].second) - std ::max (a[1 ].second, a[2 ].second); ans = std ::min (ans, sum + 2 * std ::max (0L L, std ::max (xl, yl) - std ::min (xr, yr))); } while (std ::next_permutation(a.begin (), a.end ())); std ::cout << ans << std ::endl ; } return 0 ; }
本场 1456 Div1, 我只能做 AB 两题,但是我可以做的快,做的优雅的,可是我没有!
题意:给定 0-1 字符串,需要让 $p, p + k, \cdots$ 位置都变成 1,每变一个位代价是 x,或者删除最开始的字符,这样做代价是 y。
做法:我们可以把字符串反过来,然后答案就是 a[n - p]
,状态转移:不删字符的情况下,a[i]
为 $s[i], s[i - k], \cdots$ 中 0 的个数。$a[i] = \min_{1 \leq t \leq i}(a[i], a[i - t] + t * y)$,所以我们可以将 a[i] - iy
添加到 set 中,然后最小值加上 当前的 iy
就是真实的最小值。
很早就想到了做法,实现的时候写的太急了,分析不过细致,把自己整吐了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n, p, k, x, y; std ::string s; std ::cin >> n >> p >> k >> s >> x >> y; std ::reverse(s.begin (),s.end ()); std::vector<int> a(n), cnt(k); std ::set <int > S; for (int i = 0 ; i < n; ++i) { if (s[i] == '0' ) ++cnt[i % k]; a[i] = cnt[i % k] * x; if (!S.empty()) a[i] = std ::min (a[i], *S.begin () + i * y); S.insert(a[i] - i * y); } std ::cout << a[n - p] << std ::endl ; } return 0 ; }
题意:给定一个非降的序列,可以将相邻的两个变成它们的异或值,能否在最小的步数上,将这个序列不满足非降条件。
做法:注意到,如果有三个相邻的数最高位一致,那么答案必然是 1,因此本质上我们只需考虑 n = 60 的情况,所以,随便写就能过(所以我写的特别随便,然后被人 hack 了,我真的服了自己!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int solve () { int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; for (int i = 0 ; i + 2 < n; ++i) { if ((a[i] ^ a[i + 1 ]) > a[i + 2 ]) return 1 ; if (a[i] > (a[i + 1 ] ^ a[i + 2 ])) return 1 ; } int ans = INT_MAX; for (int i = 1 ; i < n; ++i) { std ::vector <int > b, c; int nb = 0 ; for (int j = i - 1 ; j >= 0 ; --j) { nb ^= a[j]; b.emplace_back(nb); } int nc = 0 ; for (int j = i; j < n; ++j) { nc ^= a[j]; c.emplace_back(nc); } for (int j = 0 ; j < b.size (); ++j) { for (int k = 0 ; k < c.size (); ++k) if (b[j] > c[k]) { ans = std ::min (ans, j + k); } } } return ans == INT_MAX ? -1 : ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::cout << solve() << std ::endl ; return 0 ; }
题意:给定序列,求选择其中部分,使得它们和最大且不超过 t,这不就是 0-1 背包吗?但是数据范围 $0 \leq n \leq 40, 0 \leq a_i, t \leq 10^9$,此题即使是多重背包,也可以用下面各种方法来做
Meet in Middle,但是实现的时候可以有以下几种实现细节:
用是 set 或 unordered set 存和更新,然后用双指针,整体复杂度 $O(n 2 ^{\frac{n}{2}})$
用 Vector 存,之后排序(只需排序一个),然后用 lower_bounded
查找,复杂度同理
用 Vector 存,保持有序,最后用双指针,复杂度 $O(2 ^{\frac{n}{2}})$
先 dfs 找到一个较好的解,然后每次更新解,用来剪枝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int solve () { int n, t; std ::cin >> n >> t; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; std ::sort(a.rbegin(), a.rend()); auto merge = [&](std ::vector <int >& a, int x) { std ::vector <int > b; for (int i = 0 , j = 0 ; i < a.size (); ++i) { while (j < a.size () && a[i] < a[j] + x) { if (a[j] + x <= t) b.emplace_back(a[j] + x); ++j; } b.emplace_back(a[i]); } swap(a, b); }; auto get = [&](int l, int r) { std ::vector <int > x(1 ); for (int i = l; i < r; ++i) { merge(x, a[i]); } return x; }; auto la = get (0 , n / 2 ), lb = get (n / 2 , n); int ib = 0 , ans = 0 ; for (int i = la.size () - 1 ; i >= 0 ; --i) { while (ib < lb.size () && la[i] + lb[ib] > t) ++ib; if (ib != lb.size ()) ans = std ::max (ans, la[i] + lb[ib]); } return ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::cout << solve() << std ::endl ;; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int solve () { int n, t; std ::cin >> n >> t; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; std ::sort(a.rbegin(), a.rend()); std ::vector <LL> b (n + 1 ) ; for (int i = n; i > 0 ; --i) b[i - 1 ] = a[i - 1 ] + b[i]; int ans = 0 ; std ::function<void (int , int )> dfs = [&](int now, int i) { if (i > n || ans == t || ans - now >= b[i]) return ; ans = std ::max (ans, now); if (now + a[i] <= t) dfs(now + a[i], i + 1 ); dfs(now, i + 1 ); }; dfs(0 , 0 ); return ans; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::cout << solve() << std ::endl ;; return 0 ; }
题意:给定 n(为 2 的幂次,且大于 2),猜测一个长为 n ,取值在 [0, n - 1]
的数列。每次可以询问,XOR i j
,OR i j
,AND i j
中的一种($i \neq j$)。询问次数不超过 n + 1。
上面 OR
和 And
用一个即可,我们这里用 And
,OR
也类似。
做法:所有的数和第一个数异或(自己跟自己异或为 0,省一次查询),如果有相同的结果,那么做一个 AND 就知道第一个数为多少了,否则所有值都出现了,那么我们可以找到一个 i 使得 $r[1] \wedge r[i] = 2^n - 1$,此时 $r[1] \And r[i] = 0$,我们再找一个数 j,求 r[i] & r[j]
以及 r[1] & r[j]
再利用 a + b = a ^ b + 2 (a & b)
就可以求出 r[1]
了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (int i = 1 ; i < n; ++i) { std ::cout << "XOR 1 " << i + 1 << std ::endl ; std ::cin >> a[i]; } int x = -1 ; std ::vector <int > b (n, -1 ) ; for (int i = 0 ; i < n; ++i) { if (b[a[i]] == -1 ) b[a[i]] = i; else { std ::cout << "AND " << b[a[i]] + 1 << " " << i + 1 << std ::endl ; int tmp; std ::cin >> tmp; x = tmp ^ a[i]; break ; } } if (x == -1 ) { int t1, t2; std ::cout << "AND " << b[1 ] + 1 << " " << b[n - 1 ] + 1 << std ::endl ; std ::cin >> t1; t1 = t1 * 2 + n - 2 ; std ::cout << "AND " << 1 << " " << b[1 ] + 1 << std ::endl ; std ::cin >> t2; t2 = t2 * 2 + 1 ; x = (n - 1 + t1 + t2) / 2 - t1; } std ::cout << "! " << x; for (int i = 1 ; i < n; ++i) std ::cout << " " << (x ^ a[i]); std ::cout << std ::endl ; return 0 ; }
其实本题另一种处理技巧,不需要 $n$ 为 2 的幂次,只需考虑异或为 1 和 2 的 i,j
。下面做法基于此想法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (int i = 1 ; i < n; ++i) { std ::cout << "XOR 1 " << i + 1 << std ::endl ; std ::cin >> a[i]; } int x = -1 ; std ::vector <int > b (n, -1 ) ; for (int i = 0 ; i < n; ++i) { if (b[a[i]] == -1 ) b[a[i]] = i; else { std ::cout << "AND " << b[a[i]] + 1 << " " << i + 1 << std ::endl ; int tmp; std ::cin >> tmp; x = tmp ^ a[i]; break ; } } if (x == -1 ) { int t1, t2; std ::cout << "AND " << 1 << " " << b[1 ] + 1 << std ::endl ; std ::cin >> t1; std ::cout << "AND " << 1 << " " << b[2 ] + 1 << std ::endl ; std ::cin >> t2; x = t1 | (t2 & 1 ); } std ::cout << "! " << x; for (int i = 1 ; i < n; ++i) std ::cout << " " << (x ^ a[i]); std ::cout << std ::endl ; return 0 ; }
题意:从 (0, 0) 开始,每次可以向上或向右走 k 个单位(但是和起点距离不能超过 d),两人轮流走,谁不能走了谁输。
显然可以简化成走一个单位,距离不超过 $\frac{d}{k}$. 没过一小会,我就想到了,若 $2 x^2 \leq \frac{d^2}{k^2} < 2 (x + 1)^2$,那么,如果 $x^2 + (x + 1)^2 \leq \frac{d^2}{k^2}$,那么先手赢,否则先手输。
注意到 $x^2 + (x + 2)^2 = 2 (x + 1)^2 + 2 > d$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { LL d, k; std ::cin >> d >> k; d = d * d / k / k; LL x = std ::sqrt (d / 2 + 0.1 ); std ::cout << (x * x + (x + 1 ) * (x + 1 ) <= d ? "Ashish\n" : "Utkarsh\n" ); } return 0 ; }
题意:给定长为 n 的由小写字母组成的字符串 a, b
,可以将 a
相邻的位置互换(因此所有位置都可以互换),也可以将长为 $k$ 且每一位都相同的字母全部变成下一个字母。
做法:一开始想排序之和贪心(这份代码 ),后来发现 n = 3, k = 2
时 aab
变成 zzy
就会出问题。我还以为思路没问题,又交了一遍,属实天真。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;bool solve () { int n, k; std ::string a, b; std ::cin >> n >> k >> a >> b; std ::sort(a.begin (), a.end ()); std ::sort(b.begin (), b.end ()); int sa = 0 , sb = 0 , t = 0 ; auto deal = [&](char c) { for (int i = 1 ; i <= k; ++i) a[sa - i] = c; sa -= k; --t; }; while (sb < n) { if (sa == n) deal(b[sb]); if (a[sa] == b[sb]) { ++sa; ++sb; } else if (a[sa] > b[sb]) { if (t == 0 ) return 0 ; deal(b[sb]); } else { if (sa + k - 1 < n && a[sa] == a[sa + k - 1 ]) { ++t; sa += k; } else return 0 ; } } return 1 ; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { std ::cout << (solve() ? "Yes\n" : "No\n" ); } return 0 ; }
此题考虑每个数的个数会更简单!怪不得他们做的这么快…
1452E :特别难的复杂度降低问题,经典问题,注意转化题意,有两个出题人各自连续讲 k 题,每个参赛者选择去听其中一个出题人讲题,参赛者感兴趣的题在一个区间 [l, r]
问每个参赛者能听的自己感兴趣的题目的总和最大值为多少,$n$:总题目数,$m$ 参赛人数。
首先这里有一个自然的 $O(n^2 m)$ 的做法,有些人利用 Codeforces 上支持的 GCC 指令过了题…。 官方题解说:对于每个参赛者的感兴趣区间 [l, r]
,某一个出题人讲题区间 [i, i + k - 1]
,当 i
在递增时,这两个区间的交会怎样变化呢?在脑子里把区间进行平移会发现,它先增,在它们的中间相交之后递减,并且还是对称的。也就是说,两个出题人它们区间的中点和参赛者近的会被参赛者选择 。因此排序之后,第一个出题人拿前缀,后一个拿后缀。所以就是求前缀和和后缀和之和的最大值。总复杂度 $O(mn + m \log m)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m, k; std ::cin >> n >> m >> k; std ::vector <pii> a (m) ; for (auto &[l, r] : a) std ::cin >> l >> r; std ::sort(a.begin (), a.end (), [](const pii &A, const pii &B) { return A.first + A.second < B.first + B.second; }); std ::vector <int > sm (m + 1 ) ; for (int i = 0 ; i + k <= n; ++i) { int cur = 0 ; for (int j = m - 1 ; j >= 0 ; --j) { cur += std ::max (0 , std ::min (i + k, a[j].second) - std ::max (i, a[j].first - 1 )); sm[j] = std ::max (sm[j], cur); } } int ans = sm[0 ]; for (int i = 0 ; i + k <= n; ++i) { int cur = 0 ; for (int j = 0 ; j < m; ++j) { cur += std ::max (0 , std ::min (i + k, a[j].second) - std ::max (i, a[j].first - 1 )); ans = std ::max (ans, cur + sm[j + 1 ]); } } std ::cout << ans << std ::endl ; return 0 ; }
有人写了不排序的 $O(n m)$ 做法,反正我时没懂。
题意:有 $a_i$ 个 $2^i$ 的数,每次可以把某个 $2^{i + 1}$ 分成两个 $2^i$,给定 $x,y$,问至少需要多少次操作可以有至少 $y$ 个数小于 $2^x$。
首先,如果一个数 $2 \leq 2^l \leq 2^x$,那么分一次多一个答案,这种称作小的,可以将 $2^l$ 分成 $2^{l - x}$ 个 $2^x$,需要的次数为 $2^{l - x} + 1$。首先注意到 $l > k$ 时,分小的性价比较高,因此我们递增的考虑 $2^l > 2^x$
若 $2^{l - x} \leq k$,全部搞一下就可以了
否则,若 $2^{l - x} > k$,这个时候我们可以看那些小的个数是否大于等于 $k$,如果是,那结束了,否则将 $2^l$ 分割成 两个 $2^{l - 1}$,再判断,若 $2^{l - 1 - x} > k$,那么最多只会用到一个 $2^{l - 1}$,否则有一个 $2^{l - 1}$ 必然全部都拿来用了。那好了再次回到了这个判断上来了。经典!
一开始以为时线段树问题,后来一看 $n < 30$,有一堆的观察之后才好下手的!每一步的观察都要准确!才能一步一步的走向正确的答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, q; std ::cin >> n >> q; std ::vector <LL> a (n) ; for (auto &x : a) std ::cin >> x; auto getans = [&](int x, LL y) -> LL { LL small = 0 , ans = 0 ; for (int i = 0 ; i <= x; ++i) { y -= a[i]; small += (a[i] << i) - a[i]; } if (y <= 0 ) return 0 ; auto add = [&](int i, LL t) { ans += (t << i - x) - t; y -= t << i - x; small += (t << i) - (t << i - x); }; int id = x + 1 ; while (id < n) { LL t = std ::min (y >> id - x, a[id]); if (t > 0 ) add(id, t); if (t < a[id]) break ; ++id; } if (id == n) return y > small ? -1 : y + ans; while (y > small && id > x) { --id; ++ans; if (y >> id - x) add(id, 1L L); if (id == x && y > 0 ) add(id, 1L L); } return y + ans; }; while (q--) { int op, x; LL y; std ::cin >> op >> x >> y; if (op == 1 ) a[x] = y; else std ::cout << getans(x, y) << std ::endl ; } return 0 ; }
题意:给定 $n \times m$ 的 0-1
矩阵,每次操作改变 $2 \times 2$ 小方块中三个位置,要求在 $nm$ 步内使得所有方块为 0。首先显然每一行可以通过 $m$ 次操作置 0,每一列同理,所以最后转化成对 $2 \times 2$ 四次内变成全 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n, m; std ::cin >> n >> m; std ::vector <std ::string > s (n) ; for (auto &x : s) std ::cin >> x; std ::vector <std ::pair<int , int >> r; auto f = [](char &x) {x = (x == '0' ? '1' : '0' );}; for (int i = n - 1 ; i > 1 ; --i) { for (int j = m - 1 ; j >= 0 ; --j) if (s[i][j] == '1' ) { r.emplace_back(i, j); r.emplace_back(i - 1 , j); f(s[i - 1 ][j]); if (j > 0 ) { r.emplace_back(i - 1 , j - 1 ); f(s[i - 1 ][j - 1 ]); } else { r.emplace_back(i - 1 , j + 1 ); f(s[i - 1 ][j + 1 ]); } } } for (int j = m - 1 ; j > 1 ; --j) { for (int i = 0 ; i < 2 ; ++i) { if (s[i][j] == '1' ) { r.emplace_back(i, j); r.emplace_back(i, j - 1 ); r.emplace_back(1 - i, j - 1 ); f(s[i][j - 1 ]); f(s[1 - i][j - 1 ]); } } } std ::deque <std ::pair<int , int >> t[2 ]; for (int i = 0 ; i < 2 ; ++i) { for (int j = 0 ; j < 2 ; ++j) { t[s[i][j] - '0' ].emplace_back(i, j); } } auto g = [&](int i) { r.emplace_back(t[i].back()); t[1 - i].emplace_front(t[i].back()); t[i].pop_back(); }; if (t[1 ].size () == 4 ) { for (int i = 0 ; i < 3 ; ++i) g(1 ); } if (t[1 ].size () == 1 ) { g(1 ); for (int i = 0 ; i < 2 ; ++i) g(0 ); } if (t[1 ].size () == 2 ) { g(1 ); for (int i = 0 ; i < 2 ; ++i) g(0 ); } if (t[1 ].size () == 3 ) { for (int i = 0 ; i < 3 ; ++i) g(1 ); } std ::cout << r.size () / 3 << std ::endl ; for (int i = 0 ; i * 3 < r.size (); ++i) { for (int j = 0 ; j < 3 ; ++j) { std ::cout << r[i * 3 + j].first + 1 << " " << r[i * 3 + j].second + 1 << " \n" [j == 2 ]; } } } return 0 ; }
题意:问一个图中是否存在 k-阶完全子图,或一个度数全大于 k 的子图。
显然,我们可以把度数小于 $k - 1$ 的节点全部踢了。即从度数从小到大的遍历,小于 $k - 1$ 踢了,等于 $k - 1$, 看这 $k$ 个点能否成为完全图,能?结束,否则,继续,直到当前度数为 $k$,或者所有点都被剔除。
没能在比赛的时候做出原因:
没有注意到删点后更新度数的次数只和边数有关,所以当时觉得复杂度过不了!
在完全图判断时不够自信(单次复杂度 $k^2$,但是注意到边数小于 $\frac{(k - 1)k}{2}$ 时不可能为完全图)。
总复杂度 $O(m \sqrt{m} \log n)$,下面代码 998ms 飘过(在死亡的边缘疯狂试探)
unordered_set
是基于 hash 表的,如果不需要集合按顺序输出,可以作为优先选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;void solve () { int n, m, k; std ::cin >> n >> m >> k; std ::vector <std ::unordered_set <int >> e (n) ; for (int i = 0 , x, y; i < m; ++i) { std ::cin >> x >> y; --x; --y; e[x].insert(y); e[y].insert(x); } std ::set <std ::pair<int , int >> d; for (int i = 0 ; i < n; ++i) d.insert({e[i].size (), i}); auto check = [&](int u) -> bool { std ::vector <int > tmp(e[u].begin (), e[u].end ()); for (int i = 0 ; i < tmp.size (); ++i) { for (int j = 0 ; j < i; ++j) { if (e[tmp[i]].count(tmp[j]) == 0 ) return false ; } } return true ; }; auto del = [&](int u) { for (auto v : e[u]) { d.erase({e[v].size (), v}); e[v].erase(u); d.insert({e[v].size (), v}); --m; } }; while (!d.empty()) { int du = d.begin ()->first; int u = d.begin ()->second; if (du >= k) { std ::cout << 1 << " " << d.size () << "\n" ; for (auto it = d.begin (); it != d.end (); ++it) { std ::cout << (it->second + 1 ) << " " ; } std ::cout << "\n" ; return ; } if (du == k - 1 && k - 1 <= m * 2 / k && check(u)) { std ::cout << 2 << "\n" ; std ::cout << u + 1 << " " ; for (auto x : e[u]) std ::cout << x + 1 << " " ; std ::cout << "\n" ; return ; } d.erase(d.begin ()); del(u); } std ::cout << "-1\n" ; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { solve(); } return 0 ; }
没能在比赛的时候做出原因:
线段树模板不太好用
没有注意到数列必然单调递减,所以处理的时候处理复杂了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;struct SegmentTree { int n; std ::vector <int > mn, tag; std ::vector <LL> sm; #define lson l, m, 2 * p #define rson m + 1, r, 2 * p + 1 void resize () { mn.resize(4 * n); tag.resize(4 * n); sm.resize(4 * n); } SegmentTree(int _n) : n(_n) { resize(); } SegmentTree(const std ::vector <int > &a) { n = a.size (); resize(); std ::function<void (int , int , int )> build = [&](int l, int r, int p) { if (l == r) { mn[p] = sm[p] = a[l - 1 ]; return ; } int m = (l + r) / 2 ; build(lson); build(rson); pull(p); }; build(1 , n, 1 ); } void pull (int p) { mn[p] = std ::min (mn[2 * p], mn[2 * p + 1 ]); sm[p] = sm[2 * p] + sm[2 * p + 1 ]; } void set (int l, int r, int p, int v) { tag[p] = mn[p] = v; sm[p] = LL(r - l + 1 ) * v; } void push (int l, int r, int p) { if (tag[p]) { int m = (l + r) / 2 ; set (lson, tag[p]); set (rson, tag[p]); tag[p] = 0 ; } } void rangeSet (int L, int R, int v, int l, int r, int p) { if (L <= l && R >= r) { set (l, r, p, v); return ; } int m = (l + r) / 2 ; push(l, r, p); if (L <= m) rangeSet(L, R, v, lson); if (R > m) rangeSet(L, R, v, rson); pull(p); } int query (int x, int & y, int l, int r, int p) { if (mn[p] > y) return 0 ; if (x <= l && sm[p] <= y) { y -= sm[p]; return r - l + 1 ; } int m = (l + r) / 2 ; push(l, r, p); int ans = 0 ; if (x <= m) ans += query(x, y, lson); ans += query(x, y, rson); return ans; } int query (int x, int y) { return query(x, y, 1 , n, 1 ); } int bounded (int v, int l, int r, int p) { if (mn[p] >= v) return r + 1 ; if (l == r) return l; int m = (l + r) / 2 ; if (mn[2 * p] >= v) return bounded(v, rson); return bounded(v, lson); } void modify (int x, int y) { int l = bounded(y, 1 , n, 1 ); if (l <= x) rangeSet(l, x, y, 1 , n, 1 ); } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, q; std ::cin >> n >> q; std ::vector <int > a (n) ; for (auto &x: a) std ::cin >> x; SegmentTree A (a) ; while (q--) { int op, x, y; std ::cin >> op >> x >> y; if (op == 1 ) A.modify(x, y); else std ::cout << A.query(x, y) << std ::endl ; } return 0 ; }
很早就知道,任意两个可以换,要考虑非正数的个数是否为奇数。
然后为奇数时,绝对值总和减去 2 倍的最大的非正数(!!!这是错了)。
后来想了半天终于知道时减去绝对值最小的数。
然后再之前的内容上改,写出了下面屎一样的 RE 代码!还 PE 了,最后 RE,吐了!
此代码为 RE 代码!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n, m; std ::cin >> n >> m; n *= m; std ::vector <int > a, b; int r = 0 ; for (int i = 0 , x; i < n; ++i) { std ::cin >> x; r += abs (x); if (x <= 0 ) a.emplace_back(-x); else b.emplace_back(x); } if (a.size () & 1 ) { r -= std ::min (*std ::min_element(a.begin (), a.end ()), *std ::min_element(b.begin (), b.end ())) * 2 ; } std ::cout << r << std ::endl ; } return 0 ; }
1447C :经典问题:假 0-1 背包,真贪心题意:给定 $n$ 件物品 $w_i$,给出一个 $k$ 件物品和在 $[\lfloor \frac{W}{2} \rfloor, W]$ 之间的一种方案
做法:对物品从大到小排序,然后贪心即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; LL w; std ::cin >> n >> w; std::vector<std::pair<int, int>> a(n); for (int i = 0 ; i < n; ++i) { std ::cin >> a[i].first; a[i].second = i + 1 ; } std ::sort(a.begin (), a.end (), std ::greater<>()); std ::vector <int > x; bool flag = false ; LL s = 0 , h = (w + 1 ) / 2 ; for (int i = 0 ; i < n; ++i) { if (s + a[i].first <= w) { x.emplace_back(a[i].second); s += a[i].first; if (s >= h) { flag = true ; break ; } } } if (flag) { std ::sort(x.begin (), x.end ()); std ::cout << x.size () << std ::endl ; for (auto i : x) std ::cout << i << " " ; std ::cout << std ::endl ; } else std ::cout << -1 << std ::endl ; } return 0 ; }
就是一个简单的 DP,写完我都有点虚…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::string a, b; std ::cin >> n >> m >> a >> b; std ::vector <std ::vector <int >> dp (n + 1 , std ::vector <int >(m + 1 )) ; int r = 0 ; for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= m; ++j) { dp[i][j] = std ::max ({0 , dp[i - 1 ][j] - 1 , dp[i][j - 1 ] - 1 }); if (a[i - 1 ] == b[j - 1 ]) dp[i][j] = std ::max (dp[i][j], dp[i - 1 ][j - 1 ] + 2 ); r = std ::max (r, dp[i][j]); } } std ::cout << r << std ::endl ; return 0 ; }
考虑最高位,如果最高位为 1,最高位为 0 的个数都大于 1,那么它们必然不连通,所以我们要将其中的一个变得不超过 1,注意不能贪心。并且注意到如果元素个数不超过 2,必然连通。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; std ::function<int (std ::vector <int >, int )> dfs = [&](std ::vector <int > a, int now) -> int { if (now < 0 || a.size () <= 2 ) return 0 ; std ::vector <int > b, c; for (auto x : a) { if ((x >> now) & 1 ) b.emplace_back(x); else c.emplace_back(x); } return std ::min (dfs(b, now - 1 ) + std ::max (0 , int (c.size () - 1 )), dfs(c, now - 1 ) + std ::max (0 , int (b.size () - 1 ))); }; std ::cout << dfs(a, 29 ) << std ::endl ; return 0 ; }
题意:N 个节点,每个节点有一个值,然后 Q 次操作:1 a b
是将 a, b 所在的群合并,2 x y
求 $x$ 所在的群中,值为 $y$ 的个数。细节优化
尽量小的向大的合并,合并完小的记得清空
std::map
优于 std::multiset
std::vector<std::map<int, int>>
优于 std::map<std::map<int, int>>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, q; std ::cin >> n >> q; std::vector<int> c(n), p(n); std ::iota(p.begin (), p.end (), 0 ); std::vector<std::map<int, int>> mp(n); for (auto &x : c) std ::cin >> x, --x; for (int i = 0 ; i < n; ++i) ++mp[i][c[i]]; std ::function<int (int )> find = [&](int x) -> int { int ans = x; while (ans != p[ans]) ans = p[ans]; while (x != ans) { int t = p[x]; p[x] = ans; x = t; } return ans; }; auto father = [&](int i, int pi) { p[i] = pi; for (auto it = mp[i].begin (); it != mp[i].end (); ++it) { mp[pi][it->first] += it->second; } mp[i].clear (); }; auto merge = [&](int i, int j) { int fi = find (i), fj = find (j); if (fi != fj) { if (mp[fi].size () < mp[fj].size ()) father(fi, fj); else father(fj, fi); } }; while (q--) { int op, a, b; std ::cin >> op >> a >> b; --a; --b; if (op == 1 ) { merge(a, b); } else { int fa = find (a); std ::cout << mp[fa][b] << std ::endl ; } } return 0 ; }
题意:在 $n \times m$ 的格点中,有些点可以走有些不行,每次能往右,下或右下中的一个方向走任意步(但是中间不能有非法点),问有多少种从左上角到右下角的走法。
显然 DP,然后用类和优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const int M = 1e9 + 7 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std::vector<std::vector<int>> a(n, std::vector<int>(m)), al(n, std::vector<int>(m)), au(n, std::vector<int>(m)), ad(n, std::vector<int>(m)); a[0 ][0 ] = 1 ; std ::vector <std ::string > s (n) ; for (auto &x : s) std ::cin >> x; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < m; ++j) { if (s[i][j] == '.' ) { if (j > 0 ) (a[i][j] += al[i][j - 1 ]) %= M; if (i > 0 ) (a[i][j] += au[i - 1 ][j]) %= M; if (i > 0 && j > 0 ) (a[i][j] += ad[i - 1 ][j - 1 ]) %= M; al[i][j] = au[i][j] = ad[i][j] = a[i][j]; if (j > 0 ) (al[i][j] += al[i][j - 1 ]) %= M; if (i > 0 ) (au[i][j] += au[i - 1 ][j]) %= M; if (i > 0 && j > 0 ) (ad[i][j] += ad[i - 1 ][j - 1 ]) %= M; } else { a[i][j] = al[i][j] = au[i][j] = ad[i][j] = 0 ; } } } std ::cout << a[n - 1 ][m - 1 ] << std ::endl ; return 0 ; }
A 题:$a_i = 1$ 即可 B 题:若存在 $i \neq j$ 使得 $a_i = a_j$,则 YES,否则 NO(考虑二进制) C 题:可以根据假设强制让 $a_{i + j}$ 与 $i + j$ 有相同的奇偶性,那么必然满足条件,此问题一般化 解法 D 题:注意到首先我们可以让数列成对相等并且 $x \otimes x \otimes y = y$,所以如果 $n$ 为奇数,必然就 YES,若 $n$ 为偶数,我们成对这样搞之后,会有两个元素可能不等,又注意到它们相等当且仅当所有值异或为 0,所以搞定
代码索然无味就不写了
给定 $n$ 种纸币,$1 = a_1 < a_2 < \cdots a_n$,且 $a_i | a_{i + 1}$,要买商品 $x$,那么可以给 $y \geq x$,找零 $y - x$,要求 $y$ 和 $y - x$ 的最少纸币表达中没有公共纸币。问所有的 $y$ 有没有种。
注意到 $y$ 和 $(y_1, \cdots, y_n)$,(其中 $y_i * a_i < a_{i + 1}$,且 $y = \sum a_i y_i$ 有一个一一对应。然后我们可以考虑 $x$ 的向量表达,然后再看 $y + x$ 和 $y$ 没公共非零项的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; LL x; std ::cin >> n >> x; std ::vector <LL> a (n) ; for (auto &x : a) std ::cin >> x; x %= a.back(); std ::map <LL, LL> mp; mp.insert({x, 1 }); for (int i = 1 ; i < n; ++i) { std ::map <LL, LL> mp2; for (auto it = mp.begin (); it != mp.end (); ++it) { mp2[it->first / a[i]] += it -> second; if (it->first % a[i]) mp2[it->first / a[i] + 1 ] += it->second; } for (int j = i + 1 ; j < n; ++j) a[j] /= a[i]; std ::swap(mp, mp2); } std ::cout << mp[0 ] + mp[1 ] << std ::endl ; return 0 ; }
详细解释放在 izlyforever
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;using pdd = std ::pair<double , double >;const double eps = 1e-12 ;int dcmp (double x) { return fabs (x) < eps ? 0 : (x > 0 ? 1 : -1 ); } bool crossLeft (const pii &op, const pii &sp, const pii &ep) { return (sp.first - op.first) * (ep.second - op.second) < (sp.second - op.second) * (ep.first - op.first); } std ::vector <pdd> convexHull (std ::vector <pdd> p) { std ::sort(p.begin (), p.end ()); p.erase(std ::unique(p.begin (), p.end ()), p.end ()); int n = p.size (); std ::vector <pdd> q (n + 1 ) ; int top = 0 ; for (int i = 0 ; i < n; ++i) { while (top > 1 && crossLeft(q[top - 1 ], p[i], q[top - 2 ])) --top; q[top++] = p[i]; } int len = top; for (int i = n - 2 ; i >= 0 ; --i) { while (top > len && crossLeft(q[top - 1 ], p[i], q[top - 2 ])) --top; q[top++] = p[i]; } top -= n > 1 ; q.resize(top); return q; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, d, c; std ::cin >> n >> d; std ::vector <pdd> p (n) ; for (auto &[x, y] : p) { std ::cin >> c >> x >> y; x = x * d / c; y = y * d / c; } auto q = convexHull(p); auto cal = [](pdd p) { return p.first * p.second; }; auto deal = [](pdd a, pdd b) -> double { a.first -= b.first; a.second -= b.second; if (a.first * a.second < 0 ) { double t = -(b.first / a.first + b.second / a.second) / 2 ; if (t > 0 && t < 1 ) return (a.first * t + b.first) * (a.second * t + b.second); } return 0 ; }; double r = -1 ; for (auto &x : q) r = std ::max (r, cal(x)); for (int i = 0 ; i != q.size (); ++i) { r = std ::max (r, deal(q[i], q[(i + 1 ) % q.size ()])); } std ::cout .precision(12 ); std ::cout << std ::fixed << r << std ::endl ; return 0 ; }
首先,如果某两列和交换,那么给它们连边,那么只要任意两列可达,那么它们的位置最后就可以交换,也就是求每个连通分支的大小,直接广搜标记也可以做,当然了用并查集会更简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const LL M = 998244353 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <LL> fac (n + 1 ) ; fac[0 ] = 1 ; for (int i = 1 ; i <= n; ++i) fac[i] = fac[i - 1 ] * i % M; std ::vector <std ::vector <int >> a (n, std ::vector <int >(n)) ; for (auto &x : a) for (auto &i : x) std ::cin >> i; auto f = [&]() { std ::vector <int > p(n); std ::iota(p.begin (), p.end (), 0 ); auto find = [&](int x) { int ans = x; while (ans != p[ans]) ans = p[ans]; while (x != ans) { int t = p[x]; p[x] = ans; x = t; } return ans; }; for (int i = 0 ; i < n; ++i) { for (int j = i + 1 ; j < n; ++j) { bool flag = true ; for (int t = 0 ; t < n; ++t) { if (a[i][t] + a[j][t] > k) { flag = false ; break ; } } if (flag) p[find (j)] = p[find (i)]; } } for (int i = 0 ; i < n; ++i) find (i); LL r = 1 ; for (int i = 0 ; i < n; ++i) { int cnt = std ::count(p.begin (), p.end (), i); r = r * fac[cnt] % M; } return r; }; LL r = f(); for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < i; ++j) { std ::swap(a[i][j], a[j][i]); } } r = r * f() % M; std ::cout << r << std ::endl ; return 0 ; }
将 $K$ 写成 $N$ 个形如 $2^{-i}, i \geq 0$ 之和(不计顺序),问有多少中写法。我们不妨将答案记作 dp[n][k]
那么显然 dp[n][k] = dp[n][2k] + dp[n - 1][2k - 2] + \cdots dp[n - k][0]
(考虑取多少个 1
,那么剩下的最少要以 $\frac{1}{2}$ 为最大值,那么就等价于剩下的数乘以 2
),所以令 s[a] = dp[a][0] + dp[a + 1][2] + \cdots dp[n][2(n - a)]
,这样我们就能迅速求出 dp[n][k]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const LL M = 998244353 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <std ::vector <int >> dp (n + 1 , std ::vector <int >(n + 1 )) ; for (int i = 0 ; i <= n; ++i) dp[i][i] = 1 ; std ::vector <int > s (n + 1 ) ; s[0 ] = 1 ; auto add = [](int &x, int y) { (x += y) >= M && (x -= M); }; for (int i = 1 ; i <= n; ++i) { for (int j = i; j > 0 ; --j) { dp[i][j] = s[i - j]; if (j % 2 == 0 ) add(s[i - (j / 2 )], dp[i][j]); } } std ::cout << dp[n][k] << std ::endl ; return 0 ; }
首先这是一个很实在的问题。给定 $n$ 个单调递增的序列,从中取 $k$ 个数,但是取数的时候每次只能在序列的最前面取,也就是取最小的。求最大的和。首先注意最多只有一个序列取了一部分,其它的要么没取,要么取完(反证),那么我们可以二分枚举其在左边还是在右边
如果直接暴力写,也就是枚举那个只取了一部分的,其它的就是一个 0-1
背包,所以总复杂度为 $O(n^2 k)$,这肯定是过不了的。但是可以分治,也就是说分成两半,一半是 0-1
背包(即要么取完要么没取),另一半是原问题的子问题!这不就有了吗,经典!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <std ::vector <LL>> a (n) ; for (auto &p : a) { int cnt; std ::cin >> cnt; p.emplace_back(0 ); for (int i = 0 , x; i < cnt; ++i) { std ::cin >> x; if (i < k) p.push_back(p.back() + x); } } std ::vector <LL> dp (k + 1 , -1e18 ) ; dp[0 ] = 0 ; auto merge = [&](int l, int r) { for (int i = l; i < r; ++i) { for (int j = k; j >= a[i].size () - 1 ; --j) { dp[j] = std ::max (dp[j], dp[j - a[i].size () + 1 ] + a[i].back()); } } }; LL ans = 0 ; std ::function<void (int , int )> divide = [&](int l, int r) { if (l + 1 >= r) { for (int i = 0 ; i < a[l].size (); ++i) ans = std ::max (ans, dp[k - i] + a[l][i]); return ; } int m = (l + r + 1 ) / 2 ; auto tmp = dp; merge(l, m); divide(m, r); dp = tmp; merge(m, r); divide(l, m); }; divide(0 , n); std ::cout << ans << std ::endl ; return 0 ; }
本来是每一层都是一个 dp
,但是 Itst 做了空间优化 知道在某一个性质的点上取得最值,那么不一定要把这个点求出来,可以在一定范围内把值都比较一遍即可,因为极值点的判断有可能相对更为复杂,这可能就是计算机的魅力吧。 另外 Jiangly 写了一个非递归的 $\sqrt{n}$ 的做法,也很犀利。
给定一个非负数列,问是否可以通过前缀减一,后缀减一的方式使得所有的数都变成 0
一开始以为只需中间的数大于两边的最小值之和就可以,后来发现不对,然后通过自己想了一个例子,然后想到了正确做法,开心 这个问题等价与给定 $a$,求非负序列 $p, q$ 满足 $p + q = a$, $p$ 单调减,$q$ 单调递增(在保证条件下,使得$p$ 尽量大)
做法:看相邻两个数,比如左边比右边大,那么必然右边至少要做后缀减一的操作它们的差值次,也就是说,后面所有的数都要减去这个差值。反之同理,所以搞两个变量,一个是左边累减去(可以用剩余多少来标记),一个是右边累减。没跑一步判断一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas = 1 ; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; bool flag = true ; int now = a[0 ], cur = 0 ; for (int i = 1 ; i < n; ++i) { if (a[i] < cur) { flag = false ; break ; } if (a[i] > a[i - 1 ]) { cur += a[i] - a[i - 1 ]; } else { now -= a[i] - a[i - 1 ]; if (now < 0 ) { flag = false ; break ; } } } std ::cout << (flag ? "YES\n" : "NO\n" ); } return 0 ; }
1445D :一个 trival 的脑力问题给定长为 $2n$ 的序列 $a$,分成两个长度为 $n$ 的序列 $p, q$ 然后 $p$ 非降,$q$ 非升,定义 $f(p, q) = \sum_{i = 1}^n |p_i - q_i|$,问所有的 $f(p, q)$ 的和为多少。
不妨设 $a$ 是有序的,平均分两半,如果 $p$ 在左边取了 $k$ 个元素,那么 $q$ 必然在右边取了 $k$ 个元素,所以无论哪种情况,$f(p, q)$ 是常数。所以结论就显然了!所以可以搞个升级版!。
答案就是 $\binom{2n}{n} \sum_{i = 1}^{n} (a_{i + n} - a_i)$,代码就不贴了。推公式把我推吐了。
此题已经被我魔改了,哈哈哈
给定 $1 \leq a_i \leq n$,求两两不同的正整数 $b_i$,使得 $\sum_{i = 1}^n |a_i - b_i|$ 最小。 显然只要 $b$ 的值域确定了,答案就确定了,且 $1 \leq b_i < 2n$。所以就可以对 $a_i$ 排序,然后再 1 ~ 2n - 1
中选 $n$ 个数,使得结果最小。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; std ::vector <int > dp (n + 1 , 1e9 ) ; std ::sort(a.begin (), a.end ()); dp[0 ] = 0 ; for (int i = 1 ; i < 2 * n; ++i) { for (int j = n; j > 0 ; --j) dp[j] = std ::min (dp[j], dp[j - 1 ] + abs (a[j - 1 ] - i)); } print (dp[n]); } return 0 ; }
在固定数组的一些元素的条件下,最小改变多少数,使得数列严格单调递增(减去标号就变成 不严格 递增了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <int > a (n + 2 ) ; a[0 ] = -1e9 - 2 , a[n + 1 ] = 1e9 + 2 ; for (int i = 1 ; i <= n; ++i) { std ::cin >> a[i]; a[i] -= i; } std ::vector <int > b (k + 2 ) ; b[0 ] = 0 , b[k + 1 ] = n + 1 ; for (int i = 1 ; i <= k; ++i) std ::cin >> b[i]; for (int i = 2 ; i <= k; ++i) if (a[b[i]] < a[b[i - 1 ]]){ print (-1 ); return 0 ; } int r = n - k; for (int i = 0 ; i <= k; ++i) { int now = 0 ; std ::vector <int > c; for (int j = b[i] + 1 ; j < b[i + 1 ]; ++j) { if (a[j] >= a[b[i]] && a[j] <= a[b[i + 1 ]]) { auto it = std ::upper_bound(c.begin (), c.end (), a[j]); if (it == c.end ()) c.push_back(a[j]); else *it = a[j]; } } r -= c.size (); } print (r); return 0 ; }
题意大致可以转化成:有 $n$ 个人,每个人有 $m$ 个值可以选择,问如何选择才能使得他们的最大值减最小值最小。
做法就是把所有可能的选择进行排序(二维,一维存值,一维存人),然后看所有人至少一次选择的时候最大值和最小值的差是多少。相当于左右两个指针在跑,复杂度为 $O(nm)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int m = 6 ; std ::vector <int > a (m) ; for (auto &x : a) std ::cin >> x; std ::sort(a.begin (), a.end ()); int n; std ::cin >> n; std ::vector <int > b (n) ; for (auto &x : b) std ::cin >> x; std ::vector <pii> p; for (int i = 0 ; i < m; ++i) { for (int j = 0 ; j < n; ++j) { p.push_back({b[j] - a[i], j}); } } std ::sort(p.begin (), p.end ()); std ::vector <int > cnt (n) ; int r = 1e9 + 2 ; for (int i = 0 , j = 0 , x = 0 ; i < p.size (); ++i) { while (x < n && j < p.size ()) { if (cnt[p[j].second] == 0 ) ++x; ++cnt[p[j].second]; ++j; } if (x < n) break ; if (--cnt[p[i].second] == 0 ) --x; r = std ::min (r, p[j - 1 ].first - p[i].first); } print (r); return 0 ; }
一开始想枚举最小值,三分法来做(但是我知道凸性一般是不成立的)
将 $1 ~ n$ 个元素进行加入和提出操作(每个元素一次,共 $2n$)次,并且每次出的是当前集合中最小的值。 给定一个进出序列,和出的时候的元素值,求进的元素值(不合理的输出:NO)。
注意到每出一个元素,那么集合中剩下的元素都要大于出的元素,但是这个信息可以由顶元素上加限制来承载。当在加入一个新元素后,这个限制又被暂时的隐藏了。所以存在当前顶元素上是特别优质的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; int now = 0 ; std ::vector <int > r (n) ; std ::stack <pii> A; for (int i = 0 , x; i < 2 * n; ++i) { char op; std ::cin >> op; if (op == '+' ) A.push({now++, 0 }); else { std ::cin >> x; if (A.empty() || A.top().second > x) { print ("NO" ); return 0 ; } else { r[A.top().first] = x; A.pop(); if (!A.empty()) A.top().second = std ::max (A.top().second, x); } } } print ("YES" ); for (auto x : r) std ::cout << x << " " ; println ; return 0 ; }
此题一开始题意理解有点问题,最后竟然还 PA,结果最终评测 WA 了。
对任意 $1 \leq x \leq K$ 求 $\sum_{1 \leq i < j \leq n} (a_i + a_j)^x$
注意到
然后二项式展开即可。
若此题 $n$ 比较小,$k$ 比较大($n$ 特别小时直接 $n^2 \log k$ 就没啥意思了),注意到二项式展开之后是个卷积形式,所以用 NTT 有 $O(nk + k \log k)$ 的做法。例如 $n < 3 \cdot 10^4, k < 10^5$ (时限 5s)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const LL M = 998244353 ;const LL inv2 = (M + 1 ) / 2 ;LL powMod (LL x, LL n) { LL r (1 ) ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <LL> a (n) ; for (auto &x : a) std ::cin >> x; std::vector<LL> fac(k + 1), ifac(k + 1); fac[0 ] = ifac[0 ] = 1 ; for (int i = 1 ; i <= k; ++i) fac[i] = fac[i - 1 ] * i % M; ifac[k] = powMod(fac[k], M - 2 ); for (int i = k; i > 0 ; --i) ifac[i - 1 ] = ifac[i] * i % M; std ::vector <LL> s (k + 1 ) ; for (int i = 0 ; i < n; ++i) { LL p = 1 ; for (int j = 0 ; j <= k; ++j) { s[j] += p; if (s[j] >= M) s[j] -= M; p = p * a[i] % M; } } auto C = [&](int n, int m) { return fac[n] * ifac[m] % M * ifac[n - m] % M; }; for (int i = 1 ; i <= k; ++i) { int r = 0 ; for (int j = 0 ; j <= i; ++j) { r = (r + C(i, j) * s[j] % M * s[i - j] % M) % M; } r = (r - powMod(2 , i) * s[i]) % M; if (r < 0 ) r += M; r = r * inv2 % M; print (r); } return 0 ; }
求 $MEX(MEX(L, R)_{1 \leq L \leq R \leq n}$),其中 $MEX(L, R)$ 为使得 $a_L, a_{L + 1} \cdots, a_{R}$ 中没出现的最小正整数。
做法:先求出所有 MEX(i, n),这是能在 $O(n)$ 时间复杂度解决的(因为 MEX 会随着 i 递减而增大,并且值域不超过 $n$)。然后我们删除尾部的点,那么在从右往左首次出现 $Mex(i, n) > a[n]$ 的 pre[n] + 1 ~ i
这一段的值都要改成 a[n]
,这里 pre[x]
表示 x
位置前一个值为 a[x]
的位置。那么区间线段树就搞定了。
没在比赛时候写出,不想写了!
求 $\sum_{1 \leq L \leq R \leq n} MEX(L, R)$),其中 $MEX(L, R)$ 为使得 $a_L, a_{L + 1} \cdots, a_{R}$ 中没出现的最小自然数
同理与上面做法,代码不写了,懒得写线段树。
有的 MEX 定义包含 0, 有的不包含,无所谓啦。
1433G : 将某条边的权值置零下最短路径
我一开始以为是缩点… 想太多了,做法就是先求出任意两点的距离,然后边 x -> y
置零可以看成 a -> b
多了两种选择 a -> x - > y - > b
和 a -> y -> x -> b
就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); auto cmax = [](auto &x, auto y) { if (x < y) x = y; }; auto cmin = [](auto &x, auto y) { if (x > y) x = y; }; int n, m, k; std ::cin >> n >> m >> k; std ::vector <std ::vector <int >> d (n, std ::vector <int >(n, 1e9 )) ; for (int i = 0 ; i < n; ++i) d[i][i] = 0 ; std::vector<std::tuple<int, int, int>> road(m); for (auto &[x, y, w] : road) { std ::cin >> x >> y >> w; --x; --y; cmin(d[x][y], w); d[y][x] = d[x][y]; } std::vector<std::pair<int, int>> travel(k); for (auto &[a, b] : travel) { std ::cin >> a >> b; --a; --b; } auto floyd = [&](){ for (int k = 0 ; k != n; ++k) for (int i = 0 ; i != n; ++i) for (int j = 0 ; j != n; ++j) cmin(d[i][j], d[i][k] + d[k][j]); }; floyd(); LL r = 1e9 ; for (auto [x, y, w] : road) { LL now = 0 ; for (auto [a, b] : travel) { now += std ::min ({d[a][b], d[a][x] + d[y][b], d[a][y] + d[x][b]}); } cmin(r, now); } print (r); return 0 ; }
用堆优化 Dijkstra 会更快一些,用 priority_quque 比 set 快一些。不过 set 比 priority_quque 方便很多(好遍历删除等操作)。
每次操作可以增加某个数或者减少某个数,问最小多少次操作可以让 a
的最小值不小于 b
的最大值
我们可以枚举 a
最终的最小值 t
,那么答案就是 $\sum_{a_i < t} (t - a_i) + \sum_{b_i > t} (b_i - t)$ 求导然后就知道了它是凸函数了。然后就可以三分法求了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std::vector<int> a(n), b(m); for (auto &x : a) std ::cin >> x; for (auto &x : b) std ::cin >> x; auto f = [&](int ma) { LL r = 0 ; for (auto x : a) if (x < ma) r += ma - x; for (auto x : b) if (x > ma) r += x - ma; return r; }; int l = 1 , r = 1e9 ; while (l + 2 < r) { int lm = (2l l * l + r) / 3 , rm = (l + 2l l * r + 2 ) / 3 ; if (f(lm) < f(rm)) r = rm; else l = lm; } while (l < r) { if (f(l) < f(r)) --r; else ++l; } print (f(l)); return 0 ; }
当然了此题有更简单的做法:将 a
升序,b
降序,然后答案就是 a[i] - b[i]
为正的和。
$(a \oplus x) + (x \oplus b)$ 的最小值,$a \oplus b$,此时 x = a & b
1421E :观察总结题(加减号问题)
任何情况下的答案都是 每一项前添加正负号得到的。
相邻两个之间添加的正负号必然有一个是相同的,即不会出现 +-+-+
或 -+-+-
的情况
负号个数 $m$,总个数 $n$,满足 $(n + m) \equiv 1 \mod 3$(可以通过归纳得到,并且 $n$ 个正负号的情况可以看作 $n - 1$ 个正负号中某一个改变符号并且出现两个,再塞进去)
所有的情况如上所言(可以数学归纳证明)
我们用 DP[n][3][2][2]
保存全部状态。DP[i][j][k][p]
分别表示当前 i
个位置,(i + 负数个数 mod 3
) 等于 j
且k
表示是否非法(是否存在连续的正或负),p
表示最后以为是否为正的最大结果。
例如 DP[i][j][1][1]
表示前 i
位(i
与 前 i
位负号个数的和模 3 位 j
) 且有连续的负号或者正号,且第 i
位为负号的最大和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const int N = 2e5 + 2 ;LL dp[N][3 ][2 ][2 ]; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::memset (dp, -0x3f , sizeof (dp)); auto upmax = [](LL &a, LL b) { if (a < b) a = b; }; int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; if (n == 1 ) { print (a[0 ]); return 0 ; } dp[0 ][1 ][0 ][0 ] = a[0 ]; dp[0 ][2 ][0 ][1 ] = -a[0 ]; for (int i = 1 ; i < n; ++i) { for (int j = 0 ; j < 3 ; ++j) { for (int k = 0 ; k < 2 ; ++k) { for (int p = 0 ; p < 2 ; ++p) { for (int b = 0 ; b < 2 ; ++b) { int digit = (j + 1 + b) % 3 ; upmax(dp[i][digit][k | (p == b)][b], dp[i - 1 ][j][k][p] + (b == 0 ? a[i]: -a[i])); } } } } } print (std ::max (dp[n - 1 ][1 ][1 ][0 ], dp[n - 1 ][1 ][1 ][1 ])); return 0 ; }
此题多维数组写 Vector 不方便,但是又不能开局部数组(因为太大了),所以只能全局变量啦。
由简单到复杂一步步的来,很不错的题。
1428E :经典问题,正整数划分最值问题(可看作优先队列模板题)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;LL val (const int &x, const int &n) { int xn = x / n, rn = x - xn * n; return 1l l * xn * xn * (n - rn) + 1l l * (xn + 1 ) * (xn + 1 ) * rn; } LL cmpVal (const pii &A) { return val(A.first, A.second) - val(A.first, A.second + 1 ); } class cmp {public : bool operator () (const pii &lhs, const pii &rhs) const { return cmpVal(lhs) < cmpVal(rhs); } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::priority_queue<pii, std ::vector <pii>, cmp> Q; for (int i = 0 , x; i < n; ++i) { std ::cin >> x; Q.push({x, 1 }); } while (n < k) { ++n; auto [x, cnt] = Q.top(); Q.pop(); Q.push({x, cnt + 1 }); } LL r = 0 ; while (!Q.empty()) { auto [x, cnt] = Q.top(); Q.pop(); r += val(x, cnt); } print (r); return 0 ; }
若此题 $k$ 特别大,可二分 cmpVal 的题来解决。
题意:给定 0-1 序列 S,设 $f(l, r)$ 表示 $S_l S_{l + 1} \cdots, S_{r}$ 中最长连续 1 的个数,求 $\sum_{l = 1}^n \sum_{r = l}^n f(l, r)$
官方题解 实在是太精彩了!这种直方图的做法真的很 Nice!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::string s; std ::cin >> n >> s; LL ans = 0 , cur = 0 ; std ::vector <int > left (n + 1 , -1 ) ; for (int i = 0 ; i < n; ++i) { if (s[i] == '0' ) ans += cur; else { int l = i, r = i; while (r < n && s[r] == '1' ) ++r; int len = 0 ; for (i = l; i < r; ++i) { ++len; cur += i - left[len]; ans += cur; left[len] = r - len; } --i; } } print (ans); return 0 ; }
我想到反向 DP (DP[i]
表示这次本次要预留的子弹数为多少),但是还是不明确怎么写。我们可以看每一波的时候至少需要多少子弹预留。然后合理性检测,最后再从头到尾走一波就知道用多少子弹。把检测和计算分两次搞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std::vector<int> a(n), l(n), r(n), dp(n); for (int i = 0 ; i < n; ++i) std ::cin >> l[i] >> r[i] >> a[i]; for (int i = n - 1 ; i >= 0 ; --i) { int need = a[i]; if (i < n - 1 && r[i] == l[i + 1 ]) need += dp[i + 1 ]; if (LL(r[i] - l[i] + 1 ) * k < need) { print (-1 ); return 0 ; } dp[i] = std ::max (0L L, need - LL(r[i] - l[i]) * k); } int cur = k; LL ans = 0 ; for (int i = 0 ; i < n; ++i) { ans += a[i]; if (cur < dp[i]) { ans += cur; cur = k; } cur = ((cur - a[i]) % k + k) % k; } print (ans); return 0 ; }
这种只能交换相邻位置的问题,一般都能转换成逆序数。我们先根据最后的状态来定义序关系,然后对应赋值,再求逆序数就好了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;struct TreeArray { std ::vector <LL> s; TreeArray() {} TreeArray(int n) { init(n); } void init (int n) { s.resize(n + 1 ); std ::fill (s.begin (), s.end (), 0 ); } int lowbit (int n) { return n & (-n); } void add (int id, int p) { while (id < s.size ()) { s[id] += p; id += lowbit(id); } } LL sum (int id) { LL r = 0 ; while (id) { r += s[id]; id -= lowbit(id); } return r; } int search (LL val) { LL sum = 0 ; int id = 0 ; for (int i = std ::__lg(s.size ()); ~i; --i) { if (id + (1 << i) < s.size () && sum + s[id + (1 << i)] < val) { id += (1 << i); sum += s[id]; } } return ++id; } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::string s; std ::cin >> n >> s; std ::vector <int > tmp[26 ]; int now = 0 ; for (int i = s.size () - 1 ; i >= 0 ; --i) tmp[s[i] - 'a' ].emplace_back(++now); std::vector<int> a(n + 1), p(n + 1); for (int i = n; i > 0 ; --i) { a[i] = tmp[s[i - 1 ] - 'a' ].back(); tmp[s[i - 1 ] - 'a' ].pop_back(); } for (int i = 1 ; i <= n; ++i) p[a[i]] = i; TreeArray A (n) ; LL r = 0 ; for (int i = n; i > 0 ; --i) { r += A.sum(p[i]); A.add(p[i], 1 ); } print (r); return 0 ; }
给定 $1 \leq n, m \leq 10^{18}$ 和素数 $p$,求所有 $0 \leq i \leq n, 0 \leq j \leq \min(i,m)$ 中有多少对 $(i, j)$ 满足 ${i \choose j}$ 是 $p$ 的倍数。
${i \choose j}$ 是 $p$ 的倍数当且仅当 $i, j$ 的 $p$ 进制中至少有一位 $x$ 满足 $i_x < j_x$(利用 Lucas 定理显然)。
所以最终答案就是
其中 $k = \max(0, n - m)$,$f(n, m)$ 表示 $i$ 的 每一位都满足 $i_x \geq j_x$ 的方案数,注意到此时必有 $i > j$,所以这里的 $i, j$ 限制分别是 $0 \leq i \leq n, 0 \leq j \leq m$. 并且可以让 $m = \min(m, n)$,我们可以从最低位开始 DP 就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;const LL M = 1e9 + 7 ;LL inv2 = (M + 1 ) / 2 ; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas, p; std ::cin >> cas >> p; while (cas--) { LL n, m; std ::cin >> n >> m; m = std ::min (m, n); LL r = (2 * n + 2 - m) % M * ((m + 1 ) % M) % M * inv2 % M; std ::vector <int > an, bm; auto digit = [&]() { int r = 0 ; while (n) { an.emplace_back(n % p); bm.emplace_back(m % p); n /= p; m /= p; ++r; } return r; }; int d = digit(); LL dp[d + 1 ][2 ][2 ]; for (int i = 0 ; i < 4 ; ++i) dp[0 ][i / 2 ][i % 2 ] = 1 ; for (int i = 0 ; i < d; ++i) { dp[i + 1 ][0 ][0 ] = (p + 1 ) * p / 2 * dp[i][0 ][0 ] % M; dp[i + 1 ][0 ][1 ] = ((2 * p - bm[i] + 1 ) * bm[i] / 2 * dp[i][0 ][0 ] + (p - bm[i]) * dp[i][0 ][1 ]) % M; dp[i + 1 ][1 ][0 ] = ((an[i] + 1 ) * an[i] / 2 * dp[i][0 ][0 ] + (an[i] + 1 ) * dp[i][1 ][0 ]) % M; dp[i + 1 ][1 ][1 ] = 0 ; if (an[i] >= bm[i]) dp[i + 1 ][1 ][1 ] += (an[i] - bm[i]) * dp[i][0 ][1 ] + dp[i][1 ][1 ]; else dp[i + 1 ][1 ][1 ] += dp[i][1 ][0 ]; bm[i] = std ::min (bm[i], an[i]); (dp[i + 1 ][1 ][1 ] += (2 * an[i] + 1 - bm[i]) * bm[i] / 2 * dp[i][0 ][0 ] + bm[i] * dp[i][1 ][0 ]) %= M; } r = (r + M - dp[d][1 ][1 ]) % M; print (r); } return 0 ; }
$f$ 是一个次数不超过 $m$ 的多项式,满足 $f(i) = a_i, i = 0, \cdots, m$,求
给定 $n, m, x$ 和 $a_0, \cdots, a_m$,其中 $1 \leq n \leq 10^9, 1 \leq m \leq 2 \cdot 10^4, 0 \leq a_i, x \leq 998244353$
做法:利用二项式反演,记 $f(k) = \sum_{i = 0}^k {k \choose i} f_i$,则 $f_k = \sum_{i = 0}^k {k \choose i} (-1)^{k-i} f(i)$ 注意到 $f(x)$ 是次数不超过 $m$ 的多项式,所以 $f_{m + 1} = f_{m + 2} = \cdots = 0$
上述式子最后一项,只有 $m + 1$ 项,$f_0, \cdots, f_m$ 可以由 NTT 计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;const int N = 2e4 + 2 ;const LL M = 998244353 ;const LL ROOT = 3 ;LL powMod (LL x, LL n) { LL r (1 ) ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } LL fac[N], ifac[N]; void init () { fac[0 ] = 1 ; for (int i = 1 ; i < N; ++i) fac[i] = fac[i - 1 ] * i % M; ifac[N - 1 ] = powMod(fac[N - 1 ], M - 2 ); for (int i = N - 1 ; i; --i) ifac[i - 1 ] = ifac[i] * i % M; } void bitreverse (std ::vector <LL> &a) { for (int i = 0 , j = 0 ; i != a.size (); ++i) { if (i > j) std ::swap(a[i], a[j]); for (int l = a.size () >> 1 ; (j ^= l) < l; l >>= 1 ); } } void ntt (std ::vector <LL> &a, bool isInverse = false ) { LL g = powMod(ROOT, (M - 1 ) / a.size ()); if (isInverse) { g = powMod(g, M - 2 ); LL invLen = powMod(LL(a.size ()), M - 2 ); for (auto & x: a) x = x * invLen % M; } bitreverse(a); std ::vector <LL> w (a.size (), 1 ) ; for (int i = 1 ; i != w.size (); ++i) w[i] = w[i - 1 ] * g % M; auto addMod = [](LL x, LL y) { return (x += y) >= M ? x -= M : x; }; for (int step = 2 , half = 1 ; half != a.size (); step <<= 1 , half <<= 1 ) { for (int i = 0 , wstep = a.size () / step ; i != a.size (); i += step ) { for (int j = i; j != i + half; ++j) { LL t = (a[j + half] * w[wstep * (j - i)]) % M; a[j + half] = addMod(a[j], M - t); a[j] = addMod(a[j], t); } } } } void mul (std ::vector <LL>& a, std ::vector <LL> b) { int sz = 1 , tot = a.size () + b.size () - 1 ; while (sz < tot) sz *= 2 ; a.resize(sz); b.resize(sz); ntt(a); ntt(b); for (int i = 0 ; i != sz; ++i) a[i] = a[i] * b[i] % M; ntt(a, 1 ); a.resize(tot); } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); init(); int m, n, x; std ::cin >> n >> m >> x; std::vector<LL> a(m + 1), b(m + 1); for (int i = 0 ; i <= m; ++i) { std ::cin >> a[i]; a[i] = ifac[i] * a[i] % M; b[i] = (i % 2 == 0 ) ? ifac[i] : M - ifac[i]; } mul(a, b); a.resize(m + 1 ); LL r = 0 , Anx = 1 ; for (int i = 0 ; i <= m; ++i) { r += Anx * a[i] % M; Anx = Anx * (n - i) % M * x % M; } print (r % M); return 0 ; }
题意:在一棵树上选择两个点,使得任意点到这两点的最小值的最大值最小。
如果仅仅选择一个点,那么这个问题就是树的重心。
对此情况,将直径按照中点切开,然后分别求两颗子树的中心, 参考资料
证明:如果 u,v 是满足条件的两个点,那么把他们沿着中间切开必然最优,然后反证它们在树的直径上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::vector <std ::vector <int >> e (n) ; for (int i = 1 , u, v; i < n; ++i) { std ::cin >> u >> v; --u; --v; e[u].emplace_back(v); e[v].emplace_back(u); } std ::vector <int > d (n) ; auto bfs = [&](int x) -> int { std ::fill (d.begin (), d.end (), -1 ); std ::queue <int > Q; d[x] = 0 ; Q.push(x); while (!Q.empty()) { int u = Q.front(); Q.pop(); for (auto v : e[u]) if (d[v] == -1 ) { d[v] = d[u] + 1 ; Q.push(v); } } return std ::max_element(d.begin (), d.end ()) - d.begin (); }; auto f = [&](int v) { std ::vector <int > a; a.emplace_back(v); while (d[a.back()]) { for (auto v: e[a.back()]) if (d[v] + 1 == d[a.back()]) { a.emplace_back(v); break ; } } int mid = a.size () / 2 ; return std ::make_pair(a[mid - 1 ], a[mid]); }; auto [u, v] = f(bfs(bfs(0 ))); e[u].erase(std ::find (e[u].begin (), e[u].end (), v)); e[v].erase(std ::find (e[v].begin (), e[v].end (), u)); int ru = bfs(bfs(u)), ansu = (d[ru] + 1 ) / 2 ; auto ra = f(ru).second; int rv = bfs(bfs(v)), ansv = (d[rv] + 1 ) / 2 ; auto rb = f(rv).second; std ::cout << std ::max (ansu, ansv) << " " << ru + 1 << " " << rv + 1 << std ::endl ; } return 0 ; }
1422D :堆优化 Dijkstra 复杂度 $O(n \log E)$
这里的边数 E 针对本问题可以大大优化,代码可参考 Jiangly 的代码
一开始读题读错了,少看了一个连续导致浪费了很多时间(30 分钟)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;const LL M = 1e9 + 7 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::string a; std ::cin >> a; LL r = 0 , n = 0 , id = 1 , r2 = 0 ; for (auto x : a) { r = (r * 10 + id * (id - 1 ) / 2 * (x - '0' ) + r2) % M; n = (n * 10 + (x - '0' )) % M; r2 += n; ++id; } print (r); return 0 ; }
E, F 题也是好题,不过不懂。
问题描述:对每一个 $m \in [1, n]$ 求满足 $\sum_{x \in S} (x - m) = 0$ 的集合 $S$ 的个数,其中 “集合” $S$ 是由 1~n
中元素构成,元素可重,重数不超过 k。这等价于说 $\sum_{x \in S} x = \sum_{x \in T} x$ 的个数乘以 $k + 1$,其中 $S$ 是由 1 ~ m - 1
构成,$T$ 由 1 ~ n - m
构成。
做法:我们设 dp[i][j] 表示仅用 1 ~ i 中的数构成和为 j 的个数
,那么显然
于是我们保存一下前缀和,那么就可以优化计算了。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; LL M; std ::cin >> n >> k >> M; std ::vector <std ::vector <LL>> dp (n) ; dp[0 ].emplace_back(1 ); for (int i = 1 ; i < n; ++i) { int mx = (i + 1 ) * i / 2 * k; dp[i] = dp[i - 1 ]; dp[i].resize(mx + 1 ); for (int j = i; j <= mx; ++j) dp[i][j] += dp[i][j - i]; for (int j = mx; j >= (k + 1 ) * i; --j) dp[i][j] -= dp[i][j - (k + 1 ) * i] %= M; for (auto &x : dp[i]) x %= M; } for (int x = 1 ; x <= n; ++x) { LL r = 0 ; int tx = std ::min (x - 1 , n - x); for (int j = (tx + 1 ) * tx / 2 * k; j >= 0 ; --j) r += dp[x - 1 ][j] * dp[n - x][j] % M; r = (r * (k + 1 ) % M + M - 1 ) % M; print (r); } return 0 ; }
1408D :二维处理问题,经典重要问题
一开始没有看数据范围觉得没法过,就没想这个问题,然后狮子大张口想做最后一题…
此问题可以转换成经典问题:二维平面,第一象限的 $n$ 个点,每次只能同时向左或者向下移动一个单位,问最少需要多少步,让所有的点都不在第一象限。 首先先对一个坐标(例如横坐标)进行从小到大排序,然后最终结果就是 第 i 个点的横坐标加上后面所有点的纵坐标的最大值,这 n + 1 个结果中最小的一个!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std::vector<std::pair<int, int>> a(n), b(m), t; for (auto &[x, y] : a) std ::cin >> x >> y; for (auto &[x, y] : b) std ::cin >> x >> y; std ::vector <int > r (n) ; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < m; ++j) { if (a[i].first <= b[j].first && a[i].second <= b[j].second) { t.push_back({b[j].first - a[i].first + 1 , b[j].second - a[i].second + 1 }); } } } std ::sort(t.begin (), t.end ()); std ::vector <int > mx (t.size () + 1 ) ; for (int i = t.size () - 1 ; ~i; --i) { mx[i] = std ::max (mx[i + 1 ], t[i].second); } int ans = mx[0 ]; for (int i = 0 ; i < t.size (); ++i) { ans = std ::min (ans, mx[i + 1 ] + t[i].first); } print (ans); return 0 ; }
读完题目我就知道对于 $n$ 为 2 的幂次时,可以让所有元素一致,但是后面怎么就没有想到前后各搞一次呢?我在干什么!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <std ::pair<int , int >> r; auto f = [&](int x, int len) { for (int step = 1 ; step < len; step *= 2 ) { for (int i = 0 ; i < len; i += step * 2 ) { for (int j = 0 ; j < step ; ++j) { r.push_back({i + j + x, i + j + x + step }); } } } }; int k = std ::__lg(n); f(1 , 1 << k); f(n + 1 - (1 << k), 1 << k); print (r.size ()); for (auto [x, y] : r) std ::cout << x << " " << y << std ::endl ; return 0 ; }
本题解基于 Soulist 的题解和 Jiangly 的代码,写这个的原因是这个题我是非常想搞清楚,然后只有转化成自己的语言才能弄清楚,所以想写下来,从而说明是真的理解了。
题意:给定 $n$ 个数 ($a_1, a_2, \cdots, a_n$),(序列元素互异,但是这个条件没啥用吧)每次操作为等概率的选择其中一个数,然后将其减一,问经过 $k$ 次操作之后这 $n$ 个数的异或和 为 $x = 0, 1, \cdots, 2^c - 1$ 的概率。
其中, $k, c \leq 16, a_i \in [k, 2^c)$,最后答案是分数,在模 998244353 的意义下就是个整数啦。
令 $sa = a_1 \oplus a_2 \cdots \oplus a_n$,然后考虑答案在 $t$ 上的改变量。
令 $d_{i,j} = a_i \oplus (a_i - j)$,$\displaystyle F(x, y) = \prod_{i = 1}^n \left( \sum_{j = 0}^k \frac{x^{d_{i,j}}}{j!} y^j \right)$ 那么我们的答案就是 $F(x,y)[y^k] \cdot k!$,其中 $x$ 上是异或卷积,$y$ 上的是普通卷积。
那么我们现在主要问题就变成如何就 $F(x,y)[y^k] \cdot k!$ 了。由于 $x$ 上的系数加法是异或加法,所以我们需要用 异或的 fwt 将它转化成普通乘法。
$x$ 上的系数可以本质是下标!下标对应的值是关于$y$的多项式,首先注意到做 fwt 仅仅是将 $x$ 上的系数做了改变,并没有改变其它内容。这样做是为了把卷积异或乘法改成普通卷积乘法。这里做 fwt 的时候可以暴力搞。
优化 1:仅有 $O(ck)$ 中 $k$ 元组
每个 $k$ 元组对应的下标对应 fwt 之后的编号只有 $2^k$ 种!
多项式乘法可以用 ln/exp
来运算
soulist 写了题解 以下是 jiangly 大佬的代码 的注释版。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include <bits/stdc++.h> constexpr int P = 998244353 ;int power (int a, int b) { int res = 1 ; for (; b > 0 ; b /= 2 , a = 1l l * a * a % P) if (b % 2 == 1 ) res = 1l l * res * a % P; return res; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k, c; std ::cin >> n >> k >> c; std::vector<int> fac(k + 1), invFac(k + 1), inv(k + 1); fac[0 ] = 1 ; for (int i = 1 ; i <= k; ++i) fac[i] = 1l l * fac[i - 1 ] * i % P; for (int i = 0 ; i <= k; ++i) invFac[i] = power(fac[i], P - 2 ); for (int i = 1 ; i <= k; ++i) inv[i] = power(i, P - 2 ); std ::map <std ::vector <int >, int > cnt; int xsum = 0 ; for (int i = 0 ; i < n; ++i) { int a; std ::cin >> a; xsum ^= a; std ::vector <int > d (k + 1 ) ; for (int j = 0 ; j <= k; ++j) d[j] = a ^ (a - j); ++cnt[d]; } std::vector<int> f(1 << c), cntm(1 << k); for (int x = 0 ; x < (1 << c); ++x) { std ::vector <int > e; for (auto [d, t] : cnt) { int mask = 0 ; for (int i = 1 ; i <= k; ++i) if (__builtin_parity(x & d[i])) mask |= 1 << (i - 1 ); if (cntm[mask] == 0 ) e.push_back(mask); cntm[mask] += t; } std ::vector <int > g (k + 1 ) ; g[0 ] = 1 ; for (auto mask : e) { std ::vector <int > a (k + 1 ) ; a[0 ] = 1 ; for (int i = 1 ; i <= k; ++i) { if (mask >> (i - 1 ) & 1 ) a[i] = P - invFac[i]; else a[i] = invFac[i]; } std ::vector <int > pw (k + 1 ) ; int t = cntm[mask]; pw[0 ] = 1 ; for (int i = 1 ; i <= k; ++i) { int res = 0 ; for (int j = 0 ; j < i; ++j) res = (res + 1l l * pw[j] * a[i - j] % P * (i - j)) % P; res = 1l l * res * t % P; for (int j = 1 ; j < i; ++j) res = (res + 1l l * (P - a[j]) * pw[i - j] % P * (i - j)) % P; pw[i] = 1l l * res * inv[i] % P; } for (int i = k; i >= 1 ; --i) { int res = 0 ; for (int j = 0 ; j <= i; ++j) res = (res + 1l l * pw[j] * g[i - j]) % P; g[i] = res; } cntm[mask] = 0 ; } f[x] = 1l l * g[k] * fac[k] % P; } for (int i = 1 ; i < (1 << c); i *= 2 ) { for (int j = 0 ; j < (1 << c); j += 2 * i) { for (int k = 0 ; k < i; ++k) { int u = f[j + k], v = f[i + j + k]; f[j + k] = (u + v) % P; f[i + j + k] = (u - v + P) % P; } } } int invn = 1l l * power(1 << c, P - 2 ) * power(n, P - 1 - k) % P; for (int i = 0 ; i < (1 << c); ++i) std ::cout << 1l l * f[i ^ xsum] * invn % P << " \n" [i == (1 << c) - 1 ]; return 0 ; }
给定数列 $a$,求满足元素两两互素的数列 $b$ 使得 $\sum |a_i - b_i|$ 最小
注意到 $b_i < 2 a_i$,因为否则取 $b_i = 1$ 即可。
由于 $60$ 内的素数个数为 17, 因此可以状态压缩 DP。设 dp[i][j]
表示使得 $\sum_{k = 1} ^ i |a_k - b_k|$ 最小,且$b_1 \cdots b_i$ 中所有出现的素因子的状态为 $j$。因此状态转移就是 dp[i][j | factor[k]] = min(dp[i - 1][j] + |a_i - k|)
,其中 factor[k]
与 $j$ 没有交集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; int ma = *std ::max_element(a.begin (), a.end ()) * 2 ; std ::vector <int > p; for (int i = 2 ; i < ma; ++i) { bool flag = true ; for (int j = 2 ; j * j <= i; ++j) if (i % j == 0 ) { flag = false ; break ; } if (flag) p.emplace_back(i); } std ::vector <int > factor (ma) ; for (int i = 2 ; i < ma; ++i) { for (int j = 0 ; j < p.size (); ++j) if (i % p[j] == 0 ) { factor[i] |= (1 << j); } } std ::vector <std ::vector <int >> ans (n + 1 , std ::vector <int >(1 << p.size (), 1e9 )) ; ans[0 ][0 ] = 0 ; for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j < 2 * a[i - 1 ]; ++j) { for (int k = 0 ; k < (1 << p.size ()); ++k) if ((k & factor[j]) == 0 ) { ans[i][k | factor[j]] = std ::min (ans[i][k | factor[j]], ans[i - 1 ][k] + abs (a[i - 1 ] - j)); } } } std ::vector <int > r (n) ; int now = std ::min_element(ans[n].begin (), ans[n].end ()) - ans[n].begin (); for (int i = n - 1 ; i >= 0 ; --i) { for (int j = 1 ; j < 2 * a[i]; ++j) if ((now | factor[j]) == now) { if (ans[i][now ^ factor[j]] + abs (a[i] - j) == ans[i + 1 ][now]) { r[i] = j; now ^= factor[j]; break ; } } } for (auto x : r) std ::cout << x << " " ; println ; return 0 ; }
662C :状态压缩 DP + FWT 模板给定 $n \times m$ 的 0-1 方阵,可以取反一些行和列使得最后 0 的数列最小。
首先注意到 $n < 20$,我们可以把每一列看作一个状态 i
,并且结果跟列的顺序无关。我们可以记录下初始情况每种状态数 C[i] 量。 并且每一种状态 i
对答案的贡献显然就是它的 0, 1 个数的最小值记作 g[i]
。 对于每一个行取反 S, 其实就是将一个状态 i 变成 状态 i ^ S
所以每一种行取反 S,最终的答案 $\displaystyle F(S) = \sum_{i} C[i] \cdot g[i \wedge S] = \sum_{i \wedge j = S} C[i] \cdot g[j]$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;#include <cstdio> #include <algorithm> #include <vector> const int P = 998244353 ;void add (int &x, int y) { (x += y) >= P && (x -= P); } void sub (int &x, int y) { (x -= y) < 0 && (x += P); } struct FWT { int extend (int n) { int N = 1 ; for (; N < n; N <<= 1 ); return N; } void FWTor (std ::vector <int > &a, bool rev) { int n = a.size (); for (int l = 2 , m = 1 ; l <= n; l <<= 1 , m <<= 1 ) { for (int j = 0 ; j < n; j += l) for (int i = 0 ; i < m; i++) { if (!rev) add(a[i + j + m], a[i + j]); else sub(a[i + j + m], a[i + j]); } } } void FWTand (std ::vector <int > &a, bool rev) { int n = a.size (); for (int l = 2 , m = 1 ; l <= n; l <<= 1 , m <<= 1 ) { for (int j = 0 ; j < n; j += l) for (int i = 0 ; i < m; i++) { if (!rev) add(a[i + j], a[i + j + m]); else sub(a[i + j], a[i + j + m]); } } } void FWTxor (std ::vector <int > &a, bool rev) { int n = a.size (), inv2 = (P + 1 ) >> 1 ; for (int l = 2 , m = 1 ; l <= n; l <<= 1 , m <<= 1 ) { for (int j = 0 ; j < n; j += l) for (int i = 0 ; i < m; i++) { int x = a[i + j], y = a[i + j + m]; if (!rev) { a[i + j] = (x + y) % P; a[i + j + m] = (x - y + P) % P; } else { a[i + j] = 1L L * (x + y) * inv2 % P; a[i + j + m] = 1L L * (x - y + P) * inv2 % P; } } } } std ::vector <int > Or (std ::vector <int > a1, std ::vector <int > a2) { int n = std ::max (a1.size (), a2.size ()), N = extend(n); a1.resize(N), FWTor(a1, false ); a2.resize(N), FWTor(a2, false ); std ::vector <int > A (N) ; for (int i = 0 ; i < N; i++) A[i] = 1L L * a1[i] * a2[i] % P; FWTor(A, true ); return A; } std ::vector <int > And (std ::vector <int > a1, std ::vector <int > a2) { int n = std ::max (a1.size (), a2.size ()), N = extend(n); a1.resize(N), FWTand(a1, false ); a2.resize(N), FWTand(a2, false ); std ::vector <int > A (N) ; for (int i = 0 ; i < N; i++) A[i] = 1L L * a1[i] * a2[i] % P; FWTand(A, true ); return A; } std ::vector <int > Xor (std ::vector <int > a1, std ::vector <int > a2) { int n = std ::max (a1.size (), a2.size ()), N = extend(n); a1.resize(N), FWTxor(a1, false ); a2.resize(N), FWTxor(a2, false ); std ::vector <int > A (N) ; for (int i = 0 ; i < N; i++) A[i] = 1L L * a1[i] * a2[i] % P; FWTxor(A, true ); return A; } } fwt; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std ::vector <std ::string > a (n) ; for (auto &x : a) std ::cin >> x; std::vector<int> c(1 << n), g(1 << n); for (int i = 0 ; i < m; ++i) { int r = 0 ; for (int j = 0 ; j < n; ++j) { r |= (a[j][i] - '0' ) << j; } ++c[r]; } for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < (1 << n); ++j) { if (j & (1 << i)) ++g[j]; } } for (int i = 0 ; i < (1 << n); ++i) { g[i] = std ::min (g[i], n - g[i]); } auto f = fwt.Xor(c, g); print (*std ::min_element(f.begin (), f.end ())); return 0 ; }
注意到两个数比较大小只和它最高位数字有关,如果改变第 $i$ 位,那么第 $i$ 位后面的数就可以不考虑。 可以参考 jiangly 的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; int r = 0 ; LL cnt = 0 ; for (int d = 1 ; d < 31 ; ++d) { std ::vector <int > p (n) ; std ::iota(p.begin (), p.end (), 0 ); std ::sort(p.begin (), p.end (),[&](int i, int j) { return (a[i] >> d) < (a[j] >> d) || ((a[i] >> d) == (a[j] >> d) && i < j); }); LL c0 = 0 , c1 = 0 ; for (int i = 0 , j = 0 ; i < n; i = j) { int x0 = 0 , x1 = 0 ; while (j < n && (a[p[i]] >> d) == (a[p[j]] >> d)) { if ((a[p[j]] >> (d - 1 )) & 1 ) { ++x1; c0 += x0; } else { ++x0; c1 += x1; } ++j; } } if (c0 < c1) { cnt += c0; r |= (1 << d - 1 ); } else cnt += c1; } std ::cout << cnt << " " << r << std ::endl ; return 0 ; }
上述过程也提供了一种求逆序数的方法,就是注意到任意两个数,他们总会在某一个最高位是一致的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl; using LL = long long ;const LL M = 998244353 , ROOT = 3 ;LL powMod (LL x, LL n) { LL r (1 ) ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } void bitreverse (std ::vector <LL> &a) { for (int i = 0 , j = 0 ; i != a.size (); ++i) { if (i > j) std ::swap(a[i], a[j]); for (int l = a.size () >> 1 ; (j ^= l) < l; l >>= 1 ); } } void ntt (std ::vector <LL> &a, bool isInverse = false ) { LL g = powMod(ROOT, (M - 1 ) / a.size ()); if (isInverse) { g = powMod(g, M - 2 ); LL invLen = powMod(LL(a.size ()), M - 2 ); for (auto & x: a) x = x * invLen % M; } bitreverse(a); std ::vector <LL> w (a.size (), 1 ) ; for (int i = 1 ; i != w.size (); ++i) w[i] = w[i - 1 ] * g % M; auto addMod = [](LL x, LL y) { return (x += y) >= M ? x -= M : x; }; for (int step = 2 , half = 1 ; half != a.size (); step <<= 1 , half <<= 1 ) { for (int i = 0 , wstep = a.size () / step ; i != a.size (); i += step ) { for (int j = i; j != i + half; ++j) { LL t = (a[j + half] * w[wstep * (j - i)]) % M; a[j + half] = addMod(a[j], M - t); a[j] = addMod(a[j], t); } } } } void mul (std ::vector <LL>& a, std ::vector <LL> b) { int sz = 1 , tot = a.size () + b.size () - 1 ; while (sz < tot) sz *= 2 ; a.resize(sz); b.resize(sz); ntt(a); ntt(b); for (int i = 0 ; i != sz; ++i) a[i] = a[i] * b[i] % M; ntt(a, 1 ); a.resize(tot); } const int N = 1e5 + 2 ;LL fac[N], ifac[N]; LL inv (LL a) { return a == 1 ? 1 : (M - M / a) * inv(M % a) % M; } void init () { fac[0 ] = ifac[0 ] = 1 ; for (int i = 1 ; i < N; ++i) fac[i] = fac[i - 1 ] * i % M; ifac[N - 1 ] = inv(fac[N - 1 ]); for (int i = N - 1 ; i; --i) ifac[i - 1 ] = ifac[i] * i % M; } LL binom (int n, int k) { if (n < k) return 0 ; return fac[n] * ifac[k] % M * ifac[n - k] % M; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); init(); int n; std ::cin >> n; std ::vector <int > cnt (1e5 + 2 ) ; for (int i = 0 , x; i < 2 * n; ++i) { std ::cin >> x; ++cnt[x]; } std ::vector <LL> r (1 , 1 ) ; LL inv2 = (M + 1 ) / 2 ; std ::sort(cnt.begin (), cnt.end ()); auto start = std ::lower_bound(cnt.begin (), cnt.end (), 1 ) - cnt.begin (); std ::vector <std ::pair<int , int >> q; q.push_back({cnt[start], 1 }); for (int i = start + 1 ; i < cnt.size (); ++i) { if (cnt[i] != q.back().first) q.push_back({cnt[i], 1 }); else ++q.back().second; } for (auto [x, xn] : q) if (x > 0 ) { std ::vector <LL> a (x / 2 + 1 ) ; LL p2j = 1 ; for (int j = 0 ; j <= x / 2 ; ++j) { a[j] = fac[x] * ifac[x - 2 * j] % M * ifac[j] % M * p2j % M; p2j = p2j * inv2 % M; } while (xn) { if (xn & 1 ) mul(r, a); xn >>= 1 ; mul(a, a); } } std ::vector <LL> G (n + 1 ) ; G[0 ] = 1 ; for (int i = 1 ; i <= n; ++i) G[i] = G[i - 1 ] * (2 * i - 1 ) % M; LL ret = 0 ; for (int i = 0 ; i < r.size () && i <= n; ++i) { ret += (i % 2 == 1 ? -1 : 1 ) * G[n - i] * r[i] % M; } print ((ret % M + M) % M); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl; using LL = long long ;#define lrt rt << 1 #define rrt rt << 1 | 1 #define lson l, m, lrt #define rson m + 1, r, rrt const int N = 2e5 + 5 ;const int NN = N * 3.2 ;LL sum[NN], s10[N]; int col[NN];const LL M = 998244353 ;void pushUp (int rt) { sum[rt] = sum[lrt] + sum[rrt]; if (sum[rt] >= M) sum[rt] -= M; } void pushDown (int l,int r, int rt) { if (col[rt] != -1 ) { int m = (l + r) >> 1 ; col[lrt] = col[rrt] = col[rt]; col[rt] = -1 ; sum[lrt] = (s10[m] - s10[l - 1 ] + M) * col[lrt] % M; sum[rrt] = (s10[r] - s10[m] + M) * col[rrt] % M; } } void build (int l, int r, int rt) { if (l == r) { sum[rt] = s10[r] - s10[l - 1 ]; if (sum[rt] < 0 ) sum[rt] += M; col[rt] = 1 ; return ; } col[rt] = 1 ; int m = (l + r) >> 1 ; build(lson); build(rson); pushUp(rt); } void update (int L, int R, int p, int l, int r, int rt) { if (L <= l && R >= r) { sum[rt] = (s10[r] - s10[l - 1 ] + M) * p % M; col[rt] = p; return ; } pushDown(l, r, rt); int m = (l + r) >> 1 ; if (L <= m) update(L, R, p, lson); if (R > m) update(L, R, p, rson); pushUp(rt); } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); s10[0 ] = 0 ; LL now = 1 ; for (int i = 1 ; i < N; ++i) { s10[i] = now; now = now * 10 % M; } for (int i = 1 ; i < N; ++i) { s10[i] += s10[i - 1 ]; if (s10[i] >= M) s10[i] -= M; } int n, q; std ::cin >> n >> q; build(1 , n, 1 ); while (q--) { int l, r, d; std ::cin >> l >> r >> d; update(n - r + 1 , n - l + 1 , d, 1 , n, 1 ); print (sum[1 ]); } return 0 ; }
给定一堆点,求它们的 Manhattan 距离中最大值
注意到其实任意两点的距离等于,它们到最左上角和到最下角的距离的距离差的最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;const LL M = 1e9 + 7 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; int x[n], y[n]; for (int i = 0 ; i < n; ++i) { std ::cin >> x[i] >> y[i]; } int xx[2 ] = {0 , 0 }, yy[2 ] = {0 , int (1e9 )}; int t[2 ][n]; for (int id = 0 ; id < 2 ; ++id) { for (int i = 0 ; i < n; ++i) { t[id][i] = abs (x[i] - xx[id]) + abs (y[i] - yy[id]); } } int r0 = *std ::max_element(t[0 ], t[0 ] + n) - *std ::min_element(t[0 ], t[0 ] + n); int r1 = *std ::max_element(t[1 ], t[1 ] + n) - *std ::min_element(t[1 ], t[1 ] + n); print (std ::max (r0, r1)); return 0 ; }
给定数列 $a$, 求单调递增数列 $b$ 和单调递减数列 $c$,使得 $a_i = b_i + c_i$,并且使得 $b, c$ 中的最大值最小(即 $\max(b_n, c_1)$ 最小。
如果 $a_i < a_{i+1}$ 令 $b_{i +1} = b_i + a_{i + 1} - a_i$,否则 $c_{i + 1} = c_i + a_{i + 1} - a_i$。 设 $c_1 = x$,$b_ 1 = a - x$,容易看出 $b_n = \sum_{i = 2}^n \max{0, a_{i} - a_{i-1}}$。并且每次更新只和 $l, r$ 节点有关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;LL floor (LL a, LL n) { return a < 0 ? (a - n + 1 ) / n : a / n; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; LL a[n], ret = 0 ; for (int i = 0 ; i < n; ++i) std ::cin >> a[i]; for (int i = n - 1 ; i; --i) a[i] -= a[i - 1 ]; for (int i = 1 ; i < n; ++i) ret += std ::max (0L L, a[i]); std ::cout << floor (a[0 ] + ret + 1 , 2 ) << std ::endl ; int q; std ::cin >> q; while (q--) { int l, r, x; std ::cin >> l >> r >> x; if (r != n) { ret -= std ::max (0L L, a[r]); a[r] -= x; ret += std ::max (0L L, a[r]); } if (l != 1 ) ret -= std ::max (0L L, a[l - 1 ]); a[l - 1 ] += x; if (l != 1 ) ret += std ::max (0L L, a[l - 1 ]); std ::cout << floor (a[0 ] + ret + 1 , 2 ) << std ::endl ; } return 0 ; }
如果数据范围特别大,则需要下面的处理方式,否则其实可以预处理 gcd 如果这题 $c_i = \gcd(b_i, b_{i + 1}$ 将会超级难!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; int a[n]; bool v[n] = {}; for (int i = 0 ; i < n; ++i) std ::cin >> a[i]; int id = std ::max_element(a, a + n) - a; std ::vector <int > ans; ans.push_back(a[id]); v[id] = 1 ; int now = a[id]; while (ans.size () != n) { int mx = 0 , mi = 1e9 + 2 ; for (int i = 0 ; i < n; ++i) if (!v[i]) { mx = std ::max (mx, std ::__gcd(now, a[i])); mi = std ::max (mi, std ::__gcd(now, a[i])); } if (mx == mi) break ; for (int i = 0 ; i < n; ++i) if (!v[i]) { if (mx == std ::__gcd(now, a[i])) { ans.push_back(a[i]); v[i] = 1 ; } } now = mx; } for (auto x : ans) std ::cout << x << " " ; for (int i = 0 ; i < n; ++i) if (!v[i]) std ::cout << a[i] << " " ; std ::cout << std ::endl ; } return 0 ; }
1407C :第一次过真实的交互问题注意到 $(a \mod b )> (b \mod a)$ 当且仅当 $a < b$ (这里 $a, b$ 都是正整数),并且此时 $(a \mod b) = a$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; int a[n + 1 ], mi = 1 ; for (int i = 2 , mx1, mx2; i <= n; ++i) { std ::cout << "? " << i << " " << mi << std ::endl ; std ::cin >> mx1; std ::cout << "? " << mi << " " << i << std ::endl ; std ::cin >> mx2; if (mx1 > mx2) { a[i] = mx1; } else { a[mi] = mx2; mi = i; } } a[mi] = n; std ::cout << "!" ; for (int i = 1 ; i <= n; ++i) std ::cout << " " << a[i]; std ::cout << std ::endl ; return 0 ; }
1407D :经典问题:单调栈优化 DP1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std::vector<int> h(n), dp(n); for (auto &x : h) std ::cin >> x; std ::stack <int > low, high; for (int i = 0 ; i < n; ++i) { dp[i] = (i == 0 ? 0 : dp[i - 1 ] + 1 ); while (!low.empty() && h[i] > h[low.top()]) { dp[i] = std ::min (dp[i], dp[low.top()] + 1 ); low.pop(); } if (!low.empty()) { dp[i] = std ::min (dp[i], dp[low.top()] + 1 ); if (h[i] == h[low.top()]) low.pop(); } low.push(i); while (!high.empty() && h[i] < h[high.top()]) { dp[i] = std ::min (dp[i], dp[high.top()] + 1 ); high.pop(); } if (!high.empty()) { dp[i] = std ::min (dp[i], dp[high.top()] + 1 ); if (h[i] == h[high.top()]) high.pop(); } high.push(i); } std ::cout << dp[n - 1 ] << std ::endl ; return 0 ; }
$A, B$ 分别在树上某两点,每次移动的距离最大值为 $da, db$,$A$ 先移动。 如果 $A$ 有策略在有限步后与 $B$ 在同一点,那么就 $A$ 获胜,否则 $B$ 获胜。
$A$ 胜可以分这三种情况:
$A$ 直接能到 $B$
两倍 $da$ 大于或等于 树的直径
两倍 $da$ 大于或等于 $db$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;class LinkStar {public : std ::vector <int > head, nxt, to; LinkStar(int n) { nxt.clear (); to.clear (); head = std ::vector <int >(n + 1 , -1 ); } void addedge (int u, int v) { nxt.emplace_back(head[u]); head[u] = to.size (); to.emplace_back(v); } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n, a, b, da, db; std ::cin >> n >> a >> b >> da >> db; LinkStar A (n) ; for (int i = 1 , u, v; i < n; ++i) { std ::cin >> u >> v; A.addedge(u, v); A.addedge(v, u); } std ::vector <int > d (n + 1 ) ; auto bfs = [&](int s) { std ::fill (d.begin (), d.end (), -1 ); std ::queue <int > q; q.push(s); d[s] = 0 ; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = A.head[u]; ~i; i = A.nxt[i]) { int v = A.to[i]; if (d[v] == -1 ) { d[v] = d[u] + 1 ; q.push(v); } } } return std ::max_element(d.begin (), d.end ()) - d.begin (); }; int c = bfs(a); if (db <= 2 * da || d[b] <= da) { std ::cout << "Alice\n" ; } else { c = bfs(c); std ::cout << (d[c] <= 2 * da ? "Alice" : "Bob" ) << std ::endl ; } } return 0 ; }
官方题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;struct TreeArray { std ::vector <LL> s; TreeArray() {} TreeArray(int n) { init(n); } void init (int n) { s.resize(n + 1 ); std ::fill (s.begin (), s.end (), 0 ); } int lowbit (int n) { return n & (-n); } void add (int id, int p) { while (id < s.size ()) { s[id] += p; id += lowbit(id); } } LL sum (int id) { LL r = 0 ; while (id) { r += s[id]; id -= lowbit(id); } return r; } int search (LL val, int r) { LL sum = 0 ; if (s[1 ] < val) return 0 ; int id = 0 ; for (int i = std ::__lg(r); ~i; --i) { if (id + (1 << i) < r && sum + s[id + (1 << i)] >= val) { id += (1 << i); sum += s[id]; } } return id; } }; struct TreeArray2 { int n; TreeArray B, C; TreeArray2() {} TreeArray2(int _n) : n(_n){ B.init(n); C.init(n); } void add (int id, int p) { C.add(id, p); B.add(id, (id - 1 ) * p); } void add (int l, int r, int p) { add(l, p); if (r + 1 < n) add(r + 1 , -p); } LL sum (int id) { return id * C.sum(id) - B.sum(id); } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, q; std ::cin >> n >> q; std::vector<int> a(n + 1), ans(q), left(q); std ::vector <std ::vector <int >> right (n + 1 ) ; for (int i = 1 ; i <= n; ++i) { std ::cin >> a[i]; a[i] = i - a[i]; } for (int i = 0 , x, y; i < q; ++i) { std ::cin >> x >> y; left[i] = 1 + x; right[n - y].emplace_back(i); } TreeArray2 A (n) ; for (int r = 1 ; r <= n; ++r) { if (a[r] >= 0 ) { int id = A.C.search(a[r], r + 1 ); if (id > 0 ) A.add(1 , std ::min (id, r), 1 ); } for (auto x : right[r]) { ans[x] = A.sum(left[x]); if (left[x] > 1 ) ans[x] -= A.sum(left[x] - 1 ); } } for (int i = 0 ; i < q; ++i) { std ::cout << ans[i] << std ::endl ; } return 0 ; }
1401D :用 dfs 变成有根树,求出每条边的权重,存在第二次到达的节点上,然后给权重高的边赋大值即可1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;const LL M = 1e9 + 7 ;class LinkStar {public : std ::vector <int > head, nxt, to; LinkStar(int n) { nxt.clear (); to.clear (); head = std ::vector <int >(n + 1 , -1 ); } void addedge (int u, int v) { nxt.emplace_back(head[u]); head[u] = to.size (); to.emplace_back(v); } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; LinkStar A (n) ; for (int i = 1 , x, y; i < n; ++i) { std ::cin >> x >> y; A.addedge(x, y); A.addedge(y, x); } int d[n + 1 ]; bool vis[n + 1 ] = {}; std ::function<int (int )> dfs = [&](int u) -> int { vis[u] = true ; d[u] = 1 ; for (int i = A.head[u]; ~i; i = A.nxt[i]) { if (int v = A.to[i]; !vis[v]) { d[u] += dfs(v); } } return d[u]; }; dfs(1 ); std ::vector <LL> r; for (int i = 2 ; i <= n; ++i) { r.emplace_back(LL(d[i]) * (n - d[i])); } int m; std ::cin >> m; std ::vector <LL> p (m) ; for (auto &x : p) std ::cin >> x; if (r.size () > p.size ()) { std ::vector <LL> tmp (r.size () - p.size (), 1 ) ; p.insert(p.end (), tmp.begin (), tmp.end ()); } std ::sort(r.begin (), r.end ()); std ::sort(p.begin (), p.end ()); LL s = 1 ; while (r.size () < p.size ()) { s = s * p.back() % M; p.pop_back(); } p.back() = p.back() * s % M; LL ans = 0 ; for (int i = 0 ; i != r.size (); ++i) { ans += r[i] * p[i] % M; } std ::cout << ans % M << std ::endl ; } return 0 ; }
先算左横线段,再算右横线段,左横线段被退出的时候对应位置加 -1
即可,然后根据竖线从左到右依次加入右横线段,剔除左横线段。用树状数组计算一下就可以了。
注意如果横竖直线有触碰到两端,那么答案对应要 +1
。 注意到如果数据范围很大(例如 N = 1e9
)那么可以压缩一下达到目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;const int N = 1e6 ;struct TreeArray { std ::vector <LL> s; TreeArray() {} TreeArray(int n) { s.resize(n); } int lowbit (int n) { return n & (-n); } void add (int id, int p) { while (id < s.size ()) { s[id] += p; id += lowbit(id); } } LL sum (int id) { LL r = 0 ; while (id) { r += s[id]; id -= lowbit(id); } return r; } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std ::vector <std ::pair<int , int >> la, ra; for (int i = 0 ; i < n; ++i) { int lx, rx, y; std ::cin >> y >> lx >> rx; if (lx == 0 ) la.push_back({rx, y}); else ra.push_back({lx, y}); } sort(la.begin (), la.end ()); sort(ra.begin (), ra.end ()); std::vector<std::tuple<int, int, int>> b(m); for (auto &[x, ly, ry] : b) std ::cin >> x >> ly >> ry; sort(b.begin (), b.end ()); LL r = 1 ; TreeArray A (N + 1 ) ; for (auto &[x, y] : la) { A.add(y + 1 , 1 ); if (x == N) ++r; } int lid = 0 , rid = 0 ; for (auto &[x, ly, ry] : b) { while (lid < la.size () && la[lid].first < x) { A.add(la[lid].second + 1 , -1 ); ++lid; } while (rid < ra.size () && ra[rid].first <= x) { A.add(ra[rid].second + 1 , 1 ); ++rid; } r += A.sum(ry + 1 ); if (ly != 0 ) r -= A.sum(ly); else if (ry == N) ++r; } std ::cout << r << std ::endl ; return 0 ; }
有两个人记作 Alice 和 Bob,Alice 的生命值很高,所以可以认为是无限的,Bob 的生命值为 m。两个人的攻击命中率分别为 p,q
。两个人轮流攻击对方。从 Alice 开始攻击,每次攻击的时候,如果 Alice 命中,那么能让对方的生命值减低 1,同时自己的生命值能恢复 1,如果 Bob 命中,那么能让对方的生命值减低 1,注意 Bob 不会自己回血。直到 Bob 的血量变为 0,游戏结束。Alice 想知道,游戏结束的时候,自己期望生命值。
不妨设,期望为 a[m]
, 则显然a[m] = p(1-q)(a[m-1]+1) + pq a[m-1] + (1-p)q(a[m]-1) + (1-p)(1-q)a[m]
化简一下得到 $a_m = a_{m - 1} + \frac{p - q}{p}$. 另外 $a_1 = p + (1 - p) q (a_1 - 1) + (1 - p) (1 - q) a_1$,所以 $a_1 = \frac{p - q + pq}{p}$ 即 $a_m = \frac{p - q}{p} m + q$
有两个人记作 Alice 和 Bob,生命值分别是 n,m,命中率分别为 p%,q%。两个人轮流攻击对方,从 Alice 开始攻击,每次攻击的时候,如果命中,那么能让对方的生命值减低 1,直到一方的生命值不超过 0 为止。问 Alice 胜的概率
设 a[n][m], b[m][n]
分别表示表示 Alice, Bob 胜的概率。则a[n][m] = p(1-b[n][m]) + (1-p)(1-b[n][m])
,b[n][m] = q(1-a[m-1][n] + (1-q)(1-a[m][n]
, 所以我们有(p+q-pq) a[n][m] = p(1-q) a[m][n] + (1-p)q a[m-1][n] + pq a[m-1][n-1]
1399D : 01
序列分组,使得各组相邻元素不同(主要考察复杂度,超级容易 TLE)
可以存储当前 1
和 0
的个数,然后一直跑,就是 $O(n)$ 复杂度了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::string s; std ::cin >> n >> s; int k = 0 , r[n]; std ::stack <int > id[2 ]; for (int i = 0 ; i < n; ++i) { int t = ('0' == s[i]); if (id[t].size ()) { r[i] = id[t].top(); id[t].pop(); } else { r[i] = ++k; } id[1 - t].push(r[i]); } print (k); for (int i = 0 ; i < n; ++i) std ::cout << r[i] << " " ; std ::cout << std ::endl ; } return 0 ; }
1399E1 :dfs 建树,然后就知道每条边的权重,然后有限队列贪心即可,注意是所有叶子节点到根的距离和1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;class LinkStar {public : std ::vector <int > head, nxt, to; std ::vector <LL> w; LinkStar(int n) { nxt.clear (); to.clear (); head = std ::vector <int >(n + 1 , -1 ); } void addedge (int u, int v, LL val) { nxt.emplace_back(head[u]); head[u] = to.size (); to.emplace_back(v); w.emplace_back(val); } }; struct Node { int d; LL v; Node(int _d, LL _v) : d(_d), v(_v) {} bool operator <(const Node &A) const { return (v + 1 ) / 2 * d < (A.v + 1 ) / 2 * A.d; } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; LL s; std ::cin >> n >> s; LinkStar diag (n) ; for (int i = 1 , u, v; i < n; ++i) { LL w; std ::cin >> u >> v >> w; diag.addedge(u, v, w); diag.addedge(v, u, w); } std ::priority_queue<Node> a; bool vis[n + 1 ] = {}; std ::function<int (int , LL)> dfs = [&](int u, LL val) -> int { vis[u] = true ; int cnt = 0 ; for (int i = diag.head[u]; ~i; i = diag.nxt[i]) { int v = diag.to[i]; if (!vis[v]) cnt += dfs(v, diag.w[i]); } cnt = std ::max (cnt, 1 ); s -= val * cnt; a.push({cnt, val}); return cnt; }; dfs(1 , 0 ); int r = 0 ; while (s < 0 ) { auto [cnt, val] = a.top(); s += (val + 1 ) / 2 * cnt; if (val > 1 ) a.push({cnt, val / 2 }); a.pop(); ++r; } print (r); } return 0 ; }
1399E2 : 同 E1,只是贪心的时候,枚举费用为 2 的边的个数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;class LinkStar {public : std ::vector <int > head, nxt, to, c; std ::vector <LL> w; LinkStar(int n) { nxt.clear (); to.clear (); head = std ::vector <int >(n + 1 , -1 ); } void addedge (int u, int v, LL val, int cost) { nxt.emplace_back(head[u]); head[u] = to.size (); to.emplace_back(v); w.emplace_back(val); c.emplace_back(cost); } }; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; LL s; std ::cin >> n >> s; LinkStar diag (n) ; for (int i = 1 , u, v, c; i < n; ++i) { LL w; std ::cin >> u >> v >> w >> c; diag.addedge(u, v, w, c - 1 ); diag.addedge(v, u, w, c - 1 ); } std ::vector <std ::tuple<int , int , LL>> a; bool vis[n + 1 ] = {}; std ::function<int (int , LL, int )> dfs = [&](int u, LL val, int cost) -> int { vis[u] = true ; int cnt = 0 ; for (int i = diag.head[u]; ~i; i = diag.nxt[i]) { int v = diag.to[i]; if (!vis[v]) cnt += dfs(v, diag.w[i], diag.c[i]); } cnt = std ::max (cnt, 1 ); s -= val * cnt; a.push_back({cost, cnt, val}); return cnt; }; dfs(1 , 0 , 0 ); std ::vector <LL> q[2 ]; for (auto [cost, cnt, val] : a) { while (val) { q[cost].emplace_back((val + 1 ) / 2 * cnt); val >>= 1 ; } } std ::sort(q[0 ].begin (), q[0 ].end (), std ::greater<>()); std ::sort(q[1 ].begin (), q[1 ].end (), std ::greater<>()); int r = 1e9 ; for (auto &x : q[0 ]) s += x; for (int i = 0 , j = q[0 ].size (); i <= q[1 ].size (); ++i) { while (j > 0 && s - q[0 ][j - 1 ] >= 0 ) { s -= q[0 ][--j]; } if (s >= 0 ) r = std ::min (r, 2 * i + j); if (i != q[1 ].size ()) s += q[1 ][i]; } print (r); } return 0 ; }
1399F : 区间 dp 问题
用 f[m]
表示从 a[i]
的左边界到,m
的区间个数(是被一个大的覆盖了的区间,size 一般不为 1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std::vector<std::pair<int, int>> a(n); std ::vector <int > v; for (auto &[l, r] : a) std ::cin >> l >> r; a.push_back({1 , 2e5 }); for (auto &[l, r] : a) { v.emplace_back(l); v.emplace_back(r); } std ::sort(v.begin (), v.end ()); v.erase(std ::unique(v.begin (), v.end ()), v.end ()); for (auto &[l, r] : a) { l = std ::lower_bound(v.begin (), v.end (), l) - v.begin (); r = std ::lower_bound(v.begin (), v.end (), r) - v.begin (); } std ::sort(a.begin (), a.end (), [](const auto &lhs, const auto &rhs) { if (lhs.first == rhs.first) return lhs.second < rhs.second; return lhs.first > rhs.first; }); int dp[n + 1 ] = {}; for (int i = 0 ; i <= n; ++i) { std ::vector <int > f (a[i].second + 1 ) ; int mx = 0 , x = a[i].first - 1 ; for (int j = i - 1 ; j >= 0 ; --j) { if (a[j].second > a[i].second) continue ; while (x + 1 < a[j].first) mx = std ::max (mx, f[++x]); f[a[j].second] = std ::max (f[a[j].second], dp[j] + mx); } for (auto &t : f) mx = std ::max (mx, t); dp[i] = 1 + mx; } print (dp[n] - 1 ); } return 0 ; }
emorgan5289 大佬的代码 以及解释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> using namespace std ;multiset <array <int , 3>> a;multiset <int > s;int main () { ios_base::sync_with_stdio(0 ); cin .tie(0 ); cout .tie(0 ); int n; cin >> n; for (int i = 0 ; i < n; i++) { int l, r, t; cin >> l >> r >> t; a.insert(t-1 ? array {r, 1 , -l} : array {l, 0 , -r}); } for (auto & [_, t, x] : a) { if (t && s.upper_bound(x) != s.begin ()) s.erase(--s.upper_bound(x)), n--; if (!t) s.insert(x); } cout << n << "\n" ; }
1384D :两个人轮流取 $n$ 个数,比较它们取的数的异或值,较大的赢记 $s$ 为 $n$ 个数的异或值,如果 $s = 0$,那么显然平局,否则看与 $s$ 的最高位 异或不为 0 的数的人数 cnt,显然这个个数是奇数,所以为 $1 \mod 4$ ,那么先手赢,否则,我们看 $n - cnt$ 是否为奇数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::cin >> n; std ::vector <int > a (n) ; for (auto &x : a) std ::cin >> x; int sum = 0 ; for (auto &x : a) sum ^= x; if (sum == 0 ) { std ::cout << "DRAW\n" ; } else { int k = std ::__lg(sum); int cnt = 0 ; for (auto &x : a) if ((x >> k) & 1 ) ++cnt; if (cnt % 4 == 1 || (n - cnt) % 2 == 1 ) { std ::cout << "WIN\n" ; } else { std ::cout << "LOSE\n" ; } } } return 0 ; }
1384B :很有意思的一道模拟题,我也不知道为什么我要想这么久,太菜了1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n, k, len; std ::cin >> n >> k >> len; int d[n]; bool flag = false ; for (int i = 0 ; i < n; ++i) { std ::cin >> d[i]; if (d[i] > len) flag = true ; else d[i] = len - d[i]; } if (flag) { std ::cout << "No\n" ; continue ; } int l = 0 , r = k; for (int i = 0 ; i < n; ++ i){ int li = std ::min (k, d[i]); int ri = std ::max (k, 2 * k - d[i]); if (r < 2 * k) { l = 0 ; r = std ::max (r + 1 , ri); } else if (++l > li) flag = true ; l = std ::min (l, k); if (d[i] >= k) r = k; } std ::cout << (flag ? "No" : "Yes" ) << std ::endl ; } return 0 ; }
洛谷 U122053 选择题 :经典问题 :每个人说另一个人是不是好人, 好人只说真话,坏人只说慌话,输出有多少种可能的情况,并输出可能的情况下,最多的好人和最少的好人数val[i]
表示第 $i$ 个人是否为好人, w[i,j]
表示$i$ 说 $j$ 是好人还是坏人,或 $j$ 说 $i$ 是好人还是坏人,那么必然 val[i] ^ val[j] = !w[i,j]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const LL M = 998244353 ;const int N = 1e6 + 2 ;int head[N], val[N], cnt, nxt[2 * N], to[2 * N];bool w[2 * N];void init () { cnt = -1 ; memset (head, -1 , sizeof (head)); memset (val, -1 , sizeof (val)); } void addedge (int u, int v, bool flag) { nxt[++cnt] = head[u]; head[u] = cnt; to[cnt] = v; w[cnt] = flag; } std::pair<int, int> dfs(int u, int flag) { val[u] = flag; int r1 = 1 , r2 = flag; for (int i = head[u]; ~i; i = nxt[i]) { int v = to[i]; if (val[v] != -1 ) { if (val[v] != (val[u] ^ w[i])) return {-1 , 0 }; } else { auto [vr1, vr2] = dfs(v, val[u] ^ w[i]); if (vr1 == -1 ) return {-1 , 0 }; r1 += vr1; r2 += vr2; } } return {r1, r2}; } std::pair<int, int> bfs(int iu, int iflag) { std ::queue <std ::pair<int , int >> q; q.push({iu, iflag}); int r1 = 0 , r2 = 0 ; while (!q.empty()) { auto [u, flag] = q.front(); q.pop(); if (val[u] != -1 ) continue ; val[u] = flag; ++r1; r2 += flag; for (int i = head[u]; ~i; i = nxt[i]) { int v = to[i]; if (val[v] != -1 ) { if (val[v] != (val[u] ^ w[i])) return {-1 , 0 }; } else q.push({v, val[u] ^ w[i]}); } } return {r1, r2}; } LL powMod (int n) { LL r = 1 , x = 2 ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); init(); int n; std ::cin >> n; for (int i = 1 , x; i <= n; ++i) { bool flag; std ::cin >> x >> flag; addedge(i, x, !flag); addedge(x, i, !flag); } int now = 0 , mx = 0 , mi = 0 ; for (int i = 1 ; i <= n; ++i) if (val[i] == -1 ) { auto [r1, r2] = bfs(i, 1 ); if (r1 == -1 ) { std ::cout << "No answer\n" ; return 0 ; } ++now; mx += std ::max (r2, r1 - r2); mi += std ::min (r2, r1 - r2); } std ::cout << powMod(now) << std ::endl ; std ::cout << mx << std ::endl ; std ::cout << mi << std ::endl ; return 0 ; }
仅考虑 $x$-轴
当$k=0$ 时,那么距离之和就是
然后我们减去 $k$ 个障碍点和其它所有点之间的距离
再加上 $k$ 个障碍点之间的距离
不妨按照 $x$-轴排序,然后把前缀和 sa[i]
求一下,再依次计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const LL M = 1e9 + 7 ;const LL inv2 = (M + 1 )/2 ;const LL inv6 = (M + 1 )/6 ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); LL n, m; int k; std ::cin >> n >> m >> k; LL a[k], b[k]; for (int i = 0 ; i < k; ++i) { std ::cin >> a[i] >> b[i]; } std ::sort(a, a + k); std ::sort(b, b + k); LL sa[k] = {a[0 ]}, sb[k] = {b[0 ]}; for (int i = 1 ; i < k; ++ i) { sa[i] = sa[i - 1 ] + a[i]; sb[i] = sb[i - 1 ] + b[i]; } auto f = [](LL m, LL n) -> LL { return m * m % M * (n - 1 ) % M * n % M * (n + 1 ) % M; }; LL r0 = (f(m, n) + f(n, m)) * inv6 % M; LL r1 = 0 ; for (int i = 0 ; i < k; ++ i) { r1 += ((a[i] - 1 ) * a[i] + (n - a[i] + 1 ) * (n - a[i])) % M; } r1 = r1 % M * m % M * inv2 % M; LL r2 = 0 ; for (int i = 0 ; i < k; ++ i) { r2 += ((b[i] - 1 ) * b[i] + (m - b[i] + 1 ) * (m - b[i])) % M; } r2 = r2 % M * n % M * inv2 % M; LL r3 = 0 ; for (int i = 0 ; i < k; ++i) { r3 += (sa[k - 1 ] - sa[i] - a[i] * (k - 1 - i)) % M; r3 += (sb[k - 1 ] - sb[i] - b[i] * (k - 1 - i)) % M; } r3 %= M; LL r = (r0 - r1 - r2 + r3) % M; if (r < 0 ) r += M; std ::cout << r << std ::endl ; return 0 ; }
$f_0 = 1, f_1 = 1, f_2 = 2, f_3 = 8$,考虑 $n+1$ 个点,如果第 $n+1$ 点与其他点没有边相连有 $f_n$ 种,如果相连,设最小的数为 $i$, 那么一边有 $f_i$ 种(注意到 $n+1$ 这个节点可以不管,因为它不跟小于 $i$ 的节点相连),另一边有 $\frac{f_{n+2-i}}{2}$,即
化简一下,可知
我们令 $g_n = f_{n+1}$,则 $g_n = 2g_{n-1} + \sum_{i=2} ^ n g_{i-1} g_{n+1-i} = 2 g_{n-1} + \sum_{i=1} ^{n-1} g_i g_{n-i}$,所以 $g_n = \frac{2}{3} g_{n-1} + \frac{1}{3} \sum_{i=0} ^n g_i g_{n-i}$。考虑 $g_n$ 的生成函数 $g(z)$:
因此 $\frac{1}{3} g^2(z) + (\frac{2}{3} z - 1) g(z) + \frac{2}{3} = 0$,从而 $g(z) = \frac{3-2z \pm \sqrt{1 - 12 z + 4z^2}}{2}$, 由 $g_1 = 2$ 知 $g(z) = \frac{3-2z - \sqrt{1 - 12 z + 4z^2}}{2}$,从而 $f(z) = f_0 + z g(z) = 1 + \frac{3}{2} z - z^2 - \frac{z}{2} \sqrt{1 - 12 z + 4 z^2}$。
因此 $(n + 1)g_{n+1} - 12 n g_n + 4(n-1)g_{n-1} = 4 g_{n-1} - 6 g_n$,整理得到 $(n+1)g_{n+1} = (12 n - 6) g_n - (4n-8)g_{n-1}$
从而 $(n+1)f_{n+2} = (12 n - 6) f_{n+1} - (4n - 8) f_n$,即递推公式 $f_{n} = \frac{ (12 n - 30) f_{n-1} - (4 n - 16) f_{n-2} }{n-1}$。
所以原问题的答案就是
仅看分子:
如果我们定义 $b_i = a_{n-1-i}$,$c_t = \sum_{i = 0} ^{t} a_{t-i} b_{i} = \sum_{i = 0} ^{t} a_{i} b_{t-i} = \sum_{i = 0} ^{t} a_{i} a_{n-1-(t-i)}$,则 $c_{n-t} = \sum_{i = 0} ^{n-t} a_{i} a_{n-1-(n-t-i)} = \sum_{i = 0} ^{n-t} a_{i} a_{i+t-1}$ 。即答案为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;const LL M = 998244353 , ROOT = 3 ;LL powMod (LL x, LL n) { LL r (1 ) ; while (n) { if (n & 1 ) r = r * x % M; n >>= 1 ; x = x * x % M; } return r; } void bitreverse (std ::vector <LL> &a) { for (int i = 0 , j = 0 ; i != a.size (); ++i) { if (i > j) std ::swap(a[i], a[j]); for (int l = a.size () >> 1 ; (j ^= l) < l; l >>= 1 ); } } void ntt (std ::vector <LL> &a, bool isInverse = false ) { LL g = powMod(ROOT, (M - 1 ) / a.size ()); if (isInverse) { g = powMod(g, M - 2 ); LL invLen = powMod(LL(a.size ()), M - 2 ); for (auto & x: a) x = x * invLen % M; } bitreverse(a); std ::vector <LL> w (a.size (), 1 ) ; for (int i = 1 ; i != w.size (); ++i) w[i] = w[i - 1 ] * g % M; auto addMod = [](LL x, LL y) { return (x += y) >= M ? x -= M : x; }; for (int step = 2 , half = 1 ; half != a.size (); step <<= 1 , half <<= 1 ) { for (int i = 0 , wstep = a.size () / step ; i != a.size (); i += step ) { for (int j = i; j != i + half; ++j) { LL t = (a[j + half] * w[wstep * (j - i)]) % M; a[j + half] = addMod(a[j], M - t); a[j] = addMod(a[j], t); } } } } std ::vector <LL> mul (std ::vector <LL> a, std ::vector <LL> b) { int sz = 1 , tot = a.size () + b.size () - 1 ; while (sz < tot) sz *= 2 ; a.resize(sz); b.resize(sz); ntt(a); ntt(b); for (int i = 0 ; i != sz; ++i) a[i] = a[i] * b[i] % M; ntt(a, 1 ); a.resize(tot); return a; } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std ::vector <LL> a (n) ; for (auto &x : a) std ::cin >> x; std ::vector <LL> b (a.rbegin(),a.rend()) ; auto c = mul(a, b); LL inv[n] = {0 , 1 }, f[n + 1 ] = {1 , 1 }; for (int i = 2 ; i <= n; ++i) inv[i] = (M - M / i) * inv[M % i] % M; for (int i = 2 ; i <= n; ++i) { f[i] = ((12 * i - 30 )* f[i - 1 ] - (4 * i - 16 ) * f[i - 2 ]) % M * inv[i - 1 ] % M; if (f[i] < 0 ) f[i] += M; } LL r = 0 ; for (int i = 2 ; i <= n; ++i) { r += f[i] * f[n - i + 2 ] % M * c[n - i] % M; } r = r * inv[4 ] % M * powMod(f[n], M - 2 ) % M; std ::cout << r << std ::endl ; return 0 ; }
注意到一段选择了 a[i]
那么后来连续小于 a[i]
的都要被选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n, maxn; std ::cin >> n; int a[2 * n]; for (int i = 0 ; i < 2 * n; ++i) { std ::cin >> a[i]; } bool dp[n + 1 ] = {1 }; for (int i = 0 , j = 0 ; i < 2 * n; i = j) { while (j < 2 * n && a[j] <= a[i]) ++j; int len = j - i; for (int k = n; k >= len; --k) { dp[k] |= dp[k - len]; } } std ::cout << (dp[n] ? "YES" : "NO" ) << std ::endl ; } return 0 ; }
1382C :设 f: 0-1string ---> 0-1string
将字符串 0 换成 1,1 换成 0,然后倒序。给定长度为 $n$ 的字符串$a, b$ 。每次可以改变 $a$ 的前缀,给出一种方案(不超过 $3n$,或者不超过 $2n$ )
注意到取 $n$ 再取 $1$,再去 $n$,这样只会改变最后一个,所以就给出了不超过 $3n$ 的方案
可以先将 $a$变成 全$0$,再把 $b$ 变成 全$0$ 的方案反序,然后再复合。变成 $0$ 的做法就是一直让前缀数字相同。
方案一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long; int main() { //freopen("in","r",stdin); std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int cas; std::cin >> cas; while (cas--) { int n; std::string a, b; std::cin >> n >> a >> b; std::vector<int> q; while (--n >= 0) { if(a[n] != b[n]) { q.emplace_back(n + 1); q.emplace_back(1); q.emplace_back(n + 1); } } std::cout << q.size(); for (auto &x : q) std::cout << " " << x; std::cout << std::endl; } return 0; }
方案二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = long long ;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int cas; std ::cin >> cas; while (cas--) { int n; std ::string a, b; std ::cin >> n >> a >> b; a += '0' ; b += '0' ; std ::vector <int > qa, qb; for (int i = 1 ; i <= n; ++i) { if (a[i] != a[i-1 ]) qa.emplace_back(i); if (b[i] != b[i-1 ]) qb.emplace_back(i); } qa.insert(qa.end (), qb.rbegin(), qb.rend()); std ::cout << qa.size (); for (auto &x : qa) std ::cout << " " << x; std ::cout << std ::endl ; } return 0 ; }
已知,$f(n) = \displaystyle \sum_{d|n} d \cdot [\gcd(d,\frac{n}{d}) == 1]$,求 $\displaystyle \sum_{n=1} ^N f(n)$
首先
其中 $g(n) = \displaystyle \sum_{d|n} d$,所以
其中 $h(n) = \sum_{i = 1} ^n g(i) = \sum_{d=1}^n d \lfloor \frac{n}{d} \rfloor$,求 $h(n)$ 有众所周知的 $O (\sqrt{n})$ 的算法,所以总时间复杂度为 $O(\sum \frac{\sqrt{N}}{l}) = O(\sqrt{N} \log N)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using LL = long long ;#define watch(x) std::cout << (#x) << " is " << (x) << std::endl const LL M = 1e9 + 7 ;const int N = 1e6 + 2 ;int mu[N];void initmu () { mu[1 ] = 1 ; for (int i = 1 ; i < N; ++i) { for (int j = i * 2 ; j < N; j += i) { mu[j] -= mu[i]; } } } int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); initmu(); auto f = [](LL n) { LL ret = 0 ; for (LL l = 1 , r; l <= n; l = r + 1 ) { r = n / (n / l); ret += ((n / l) % M) * ((l + r) % M) % M * ((r - l + 1 ) % M) % M; } return ret % M * ((M + 1 ) / 2 ) % M; }; int cas; std ::cin >> cas; while (cas--) { LL n, ans = 0 ; std ::cin >> n; for (LL i = 1 ; i * i <= n; ++i) { if (mu[i] == 0 ) continue ; ans += (M + mu[i]) * i % M * f(n / (i * i)) % M; } std ::cout << ans%M << std ::endl ; } return 0 ; }
另外一种计算思考:考虑每一个 $d$ 对答案的贡献,则
题目很容易转化成,满足所有$(a_1+\cdots+a_5)+2(b_1+\cdots+b_5) \leq N$的$a_1 \cdots a_5$之和,其中$a_i,b_i$ 均为非负整数
我一开始把 5 看作变量,从 1,2,3
一直推出 5 的公式,贼麻烦。后来 querty20002 给出了生成函数做法的 题解 。答案唯一的依赖于 $N$,即可认为答案是关于 $N$ 的数列,那么它的生成函数即为:
所以答案就是 $\sum_{i=N\%2}^{11} \binom{11}{i} \binom{\frac{N-i-5}{2}+15}{15}$,所以我们选择 Python 交题 0.0
1 2 3 4 5 6 7 8 9 import mathM = 1000000007 T = int(input()) for i in range(T): n = int(input()) r = 0 for i in range((n-5 )%2 ,12 ,2 ): r+=math.comb(11 ,i)%M*math.comb((n-i+25 )//2 ,15 )%M; print(r%M)
或者 C++ 也行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std ;using LL = long long ;constexpr LL M = 1e9 +7 ;LL inv (LL a,LL p) { return a==1 ?1 :(p-p/a)*inv(p%a,p)%p; } LL C (LL n, int k) { if (n<k) return 0 ; if (k==0 ) return 1 ; LL r = 1 ; for (int i=0 ;i<k;++i){ r = r*(n-i)%M*inv(i+1 ,M)%M; } return r; } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >> cas; while (cas--){ LL n ,r = 0 ; cin >> n; for (int i=(n+1 )%2 ;i<=11 ;i+=2 ) { r+=C(11 ,i)*C((n-i+25 )/2 ,15 )%M; } cout <<r%M<<endl ; } return 0 ; }
1215E :给定长度为 $n$ 数据范围 $[1,20]$ 的数组,求最少交换相邻位置元素使得相同的数字都紧挨着
参考 cf1215e ,注意到交换两个数字,其它数字的相对位置不发生改变
状态 dp :设 dp[mask]
表示状态为 mask
的最小交换次数,其中 mask&(1<<i)=1
表示值为 1+i
的数已经被考虑到了。所以 $dp[2^{20}-1]$ 就是我们要的结果。状态转移
其中 cnt(j,k)
表示把 所有的 j
移动到 k
前面需要的次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std ;using LL = long long ;#define watch(x) cout << (#x) << " is " << (x) << endl constexpr int N = 20 ;LL cnt[N][N],s[N],dp[1 <<N]; int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; cin >>n; for (int i=0 ,x;i<n;++i){ cin >>x;--x; for (int j=0 ;j<N;++j) if (x!=j){ cnt[x][j] += s[j]; } ++s[x]; } memset (dp,0x3f ,sizeof (dp)); dp[0 ]=0 ; for (int i=0 ;i!=(1 <<N);++i){ for (int j=0 ;j<N;++j) if (!(i&(1 <<j))){ LL s = dp[i]; for (int k=0 ;k<N;++k) if (i&(1 <<k)){ s+=cnt[j][k]; } dp[i|(1 <<j)] = min (dp[i|(1 <<j)],s); } } cout <<dp[(1 <<N)-1 ]<<endl ; return 0 ; }
以上代码基本 copy WZYYN 的代码
1228E :(二维)包容排斥原理
在 $n \times n$ 的格子中填不超过$k$的正整数,使得每行每列的最小值都为 1 的填法方案数。下图是 官方题解
无论是 dp 还是包容排斥,定义好状态是最重要的。
1148B :在一次转机的旅行中,取消 $k$ 个航班,使得旅客最晚达到终点
一开始被卡住了,想一下子吃个胖子 0.0 其实枚举起点取消的航班数,然后取最大值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n,m,ta,tb,k; cin >>n>>m>>ta>>tb>>k; int a[n],b[m]; for (int i=0 ;i<n;++i){ cin >>a[i]; a[i]+=ta; } for (int i=0 ;i<m;++i) cin >>b[i]; if (n<=k||m<=k){ cout <<-1 <<endl ; }else { int ans = 0 ; for (int i=0 ;i<=k;++i){ int id = lower_bound(b,b+m,a[i])-b; if (id+k-i>=m){ ans = -1 ;break ; } ans = max (ans,tb+b[id+k-i]); } cout <<ans<<endl ; } return 0 ; }
1251F : 组成周长固定的先单调递增后单调递减的序列
很快就能想到周长为 $2(L+x)$,其中 $L,x$ 分别表示序列最大值和长度。一开始想不明白的地方在于,如果有两个长度相同的怎么计数,后来发现相同的可以标记一个只能放左边,另一个只能放右边。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include <bits/stdc++.h> using namespace std ;using LL = long long ;constexpr LL M = 998244353 ,ROOT=3 ;LL powmod (LL x,LL n) { LL r (1 ) ; while (n){ if (n&1 ) r=r*x%M; n>>=1 ; x=x*x%M; } return r; } void bitreverse (vector <LL> &a) { for (int i=0 ,j=0 ;i!=a.size ();++i){ if (i>j) swap(a[i],a[j]); for (int l=a.size ()>>1 ;(j^=l)<l;l>>=1 ); } } void ntt (vector <LL> &a,bool isInverse=false ) { LL g = powmod(ROOT,(M-1 )/a.size ()); if (isInverse){ g = powmod(g,M-2 ); LL invLen = powmod(LL(a.size ()),M-2 ); for (auto &x:a) x=x*invLen%M; } bitreverse(a); vector <LL> w (a.size (),1 ) ; for (int i=1 ;i!=w.size ();++i) w[i] = w[i-1 ]*g%M; auto addMod = [](LL x,LL y){return (x+=y)>=M?x-=M:x;}; for (int step =2 ,half = 1 ;half!=a.size ();step <<=1 ,half<<=1 ){ for (int i=0 ;i!=a.size ();i+=step ){ for (int j=0 ;j!=half;++j){ LL t = (a[i+j+half]*w[a.size ()/step *j])%M; a[i+j+half]=addMod(a[i+j],M-t); a[i+j]=addMod(a[i+j],t); } } } } vector <LL> mul (vector <LL> a,vector <LL> b) { int sz=1 ,tot = a.size ()+b.size ()-1 ; while (sz<tot) sz*=2 ; a.resize(sz);b.resize(sz); ntt(a);ntt(b); for (int i=0 ;i!=sz;++i) a[i] = a[i]*b[i]%M; ntt(a,1 ); a.resize(tot); return a; } constexpr int N = 3e5 +2 ;LL fac[N],ifac[N]; void init () { fac[0 ]=1 ; for (int i=1 ;i<N;++i) fac[i] = fac[i-1 ]*i%M; ifac[N-1 ] = powmod(fac[N-1 ],M-2 ); for (int i=N-1 ;i;--i) ifac[i-1 ] = ifac[i]*i%M; } LL binom (int n,int k) { return fac[n]*ifac[k]%M*ifac[n-k]%M; } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); init(); int n,k,x,q; cin >>n>>k; int cnt[N] = {}; for (int i=0 ;i<n;++i){ cin >>x; ++cnt[x]; } vector <LL> ans (2 *N) ; while (k--){ cin >>x; int c1=0 ,c2=0 ; for (int i=1 ;i<x;++i){ if (cnt[i]>1 ) c2+=2 ; else if (cnt[i]==1 ) ++c1; } vector<LL> a(c1+1),b(c2+1); auto inc = [](LL &a,LL b){if ((a+=b)>=M) a-=M;}; LL p2=1 ; for (int i=0 ;i<=c1;++i){ a[i] = binom(c1,i)*p2%M; inc(p2,p2); } for (int i=0 ;i<=c2;++i) b[i] = binom(c2,i); a = mul(a,b); for (int i=0 ;i!=a.size ();++i){ inc(ans[i+x+1 ],a[i]); } } cin >>q; while (q--){ cin >>x; cout <<ans[x/2 ]<<endl ; } return 0 ; }
1251E : 花最少的钱,让所有人都投票给自己(选民跟风且贪财)
我们按照 $m$ 分层,然后从大到小记录白嫖的人,然后实在没法白嫖的,就取消费最少的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >>cas; while (cas--){ int n; cin >>n; vector <int > a[n]; for (int i=0 ,x,y;i<n;++i){ cin >>x>>y; a[x].emplace_back(y); } LL r = 0 ; priority_queue<int ,vector <int >,greater<int >> q; for (int i=n-1 ;i>=0 ;--i){ for (auto &x:a[i]) q.push(x); while (n-q.size ()<i){ r+=q.top(); q.pop(); } } cout <<r<<endl ; } return 0 ; }
1375D : MEX once more,通过修改数组的某一个值成 mex,使得数组最终非降
不妨最终变成 0~n-1
,这是不好想的,特别是紧张的比赛的时候
如果当前mex = n
即数列正好是一个排列,此时选择任意一个a[i]!=i
的位置,让 a[i]=n
,否则 mex < n
此时令a[mex]=n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >>cas; while (cas--){ int n; cin >>n; int a[n],v[n],c[n+1 ]={},step =n; for (int i=0 ;i<n;++i){ cin >>a[i]; ++c[a[i]]; v[i] = (a[i]==i); if (v[i]) --step ; } vector <int > q; int mex = 0 ,l=0 ; while (c[mex]) ++mex; while (step --){ if (mex == n){ while (v[l]) ++l; q.push_back(l+1 ); --c[a[l]]; ++c[mex]; mex = a[l]; a[l] = n; } ++c[mex]; int nmex = mex; if (--c[a[mex]]==0 &&a[mex]<mex){ nmex = a[mex]; } a[mex] = mex; v[mex] = true ; q.push_back(mex+1 ); mex = nmex; while (c[mex]) ++mex; } cout <<q.size ()<<endl ; for (auto &x:q) cout <<x<<" " ; cout <<endl ; } return 0 ; }
上面算法过度追求效率而丢失了可读性。其实可以缩短一半代码量
1119C : 选取 4 个角,反位,使得 A 矩阵变成 B 矩阵
把 A,B 异或到 A,然后就转化成 A 矩阵变成 0 矩阵的方案了。这个时候一行行的处理就搞定了!(一开始就是没思路,后来灵光一闪)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 502 ;int a[N][N];bool f (int n,int m) { for (int i=1 ;i<=n;++i){ int cnt = 0 ; for (int j=1 ;j<=m;++j){ if (a[i][j]){ ++cnt; if (i!=n){ a[i+1 ][j]^=a[i][j]; } } } if (cnt&1 ) return false ; if (i==n) return cnt==0 ; } } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n,m; cin >>n>>m; for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ cin >>a[i][j]; } } for (int i=1 ;i<=n;++i){ for (int j=1 ,x;j<=m;++j){ cin >>x; a[i][j]^=x; } } cout <<(f(n,m)?"Yes" :"No" )<<endl ; return 0 ; }
就是计算最容易听牌的数量,ABC 和 AAA 的个数和
考虑 ans[n][i][j]
表示只考虑小于 n
的情况下,有 i
个 (n-1,n)
和 j
个 n
剩余的答案。由于三个 (n,n+1,n+2)
可以转化成 (n,n,n),(n+1,n+1,n+1),(n+2,n+2,n+2)
,所以 i,j
都小于 3。且 ans[n+1][j][k] = max(ans[n][i][j]+i+(c[n+1]-i-j-k)/3)
,最后可以优化一下空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cnt,m; cin >>cnt>>m; int c[m+1 ]={}; for (int i=0 ,x;i<cnt;++i){ cin >>x; ++c[x]; } int dp[3 ][3 ],new_dp[3 ][3 ]; memset (dp,-1 ,sizeof (dp)); dp[0 ][0 ] = 0 ; for (int n=0 ;n<m;++n){ memset (new_dp,-1 ,sizeof (new_dp)); for (int i=0 ;i<3 ;++i){ for (int j=0 ;j<3 ;++j){ for (int k=0 ;k<3 ;++k){ if (i+j+k<=c[n+1 ]&&dp[i][j]>=0 ){ new_dp[j][k] = max (new_dp[j][k],dp[i][j]+i+(c[n+1 ]-i-j-k)/3 ); } } } } swap(new_dp,dp); } cout <<dp[0 ][0 ]<<endl ; return 0 ; }
1371E :给定长为 $n$ 的数组和素数 $p$,记满足 $x+i < a_{\sigma(i)},0\leq i<n$ 的排列个数为 $f(x)$,输出所有 $x$ 使得 $p \not | n$
先排序,并且注意到 $x \in (\max a_i-n,\max a_i+n)$ 之间。
记 $b_i$ 为数组中小于等于 $i$ 的元素个数。则 $f(x) = \prod\limits_{i=x}^{x+n-1} b_i-(i-x) = \prod \limits_{i=x}^{x+n-1} x-(i-b_i)$,所以我们预处理出 $i-b_i$ 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n,p; cin >>n>>p; int a[n]; for (int i=0 ;i<n;++i) cin >>a[i]; int mx = *max_element(a,a+n); int bb[n*2 +2 ]={}; int lx = mx-n; int *b = bb - lx; for (int i=0 ;i<n;++i) ++b[max (lx,a[i])]; for (int i=1 ;i<n*2 +2 ;++i) bb[i]+=bb[i-1 ]; int mp[n]={}; auto modp = [](int x,int p){ x%=p; return x<0 ?x+p:x; }; for (int i=lx;i<=mx;++i){ ++mp[modp(i-b[i],p)]; } vector <int > q; for (int i=lx;i<=mx;++i){ if (mp[modp(i,p)]==0 ){ q.emplace_back(i); } --mp[modp(i-b[i],p)]; ++mp[modp(i+n-b[i+n],p)]; } cout <<q.size ()<<endl ; for (int i=0 ;i!=q.size ();++i){ cout <<q[i]<<" \n" [i == q.size ()-1 ]; } return 0 ; }
在区间 [l,r)
中找 (a,b)
使得 $a+b = a \wedge b$ 的个数 $f(l,r)$。
显然,$f(x,x) = 0 \; (x\neq 0),\; f(2l,2r) = 3f(l,r)$,若 $l$ 为奇数,那么 $f(l,r) = f(l+1,r) + 2(g(l,r)-g(l,l))-(l==0)$,若 $r$ 为奇数,那么 $f(l,r) = f(l,r-1) + 2(g(r-1,r)-g(r-1,l))-(r==1)$ 。其中 $g(x,n)$ 表示满足下式的 $y$ 的个数:
通过比较 $x,n$ 的二进制,$g(x,n)$ 的计算是容易计算的的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); function<int (int ,int )> g = [&](int x ,int n)->int { int ret = 1 ,zeros=0 ; for (int i=1 ;i<=n;i<<=1 ){ if (n&i){ n^=i; if (!(x&n)) ret += 1 <<zeros; } if (!(x&i)) ++zeros; } return ret; }; function<LL(int ,int )> f = [&](int l ,int r)->LL{ if (l==r) return 0 ; LL ret = 0 ; if (l&1 ){ ret += (g(l,r)-g(l,l))*2 -(l==0 ); ++l; } if (r&1 ){ ret += (g(r-1 ,r)-g(r-1 ,l))*2 -(r==1 ); --r; } return ret + 3 *f(l/2 ,r/2 ); }; int cas; cin >>cas; while (cas--){ int l,r; cin >>l>>r; cout <<f(l,r+1 )<<endl ; } return 0 ; }
1373F :$f(x)$ 表示非负整数 $x$ 的十进制表示的各位数之和。求最小的非负整数 $x$ 使得 $\sum_{i=0} ^k f(x+i) = n$由于 $0 \leq k \leq 9$,也就是说 $x,x+1,\cdots,x+k$,的个位数各不相同。我们可以枚举 $x$ 的个位数,那么 $x/10,\cdots, (x+k)/10$,最多仅有两种取值。若只有一种,即没有进位(不能为 9),那么直接就可以把 $x$ 的位数和求出来,否则 $x/10$ 的个数必为 9。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const LL inf = 1e17 +2 ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); auto csum = [](int a,int b){ return (a+b)*(b-a+1 )/2 ; }; auto f = [](int n){ LL r = 0 ,d = 1 ; while (n>9 ){ r+=9 *d; d*=10 ; n-=9 ; } return r+n*d; }; auto g = [&](int a,int k,int n) -> LL{ LL r = 0 ,d = 1 ; if (n<a) return inf; while ((n-a)%k){ n-=9 *(k-a); r+=d*9 ; d*=10 ; if (n<a) return inf; } return r+d*(f((n-a)/k+1 )-1 ); }; int cas; cin >>cas; while (cas--){ int n,k; cin >>n>>k; if (k==0 ){ cout <<f(n)<<endl ; continue ; } if (k*(k-1 )/2 > n){ cout <<-1 <<endl ; continue ; } LL r = inf; for (int i=0 ;i<=9 ;++i){ if (i+k<=9 ){ int t = csum(i,i+k); if (n>=t&&(n-t)%(k+1 )==0 ) r = min (r,10 *f((n-t)/(k+1 ))+i); }else { int t = csum(i,9 )+csum(0 ,i+k-10 ); if (n>=t) r = min (r,10 *g(i+k-9 ,k+1 ,n-t)+i); } } cout <<(r==inf? -1 :r)<<endl ; } return 0 ; }
以上代码按照时间顺序(2020/6/25——now)倒序,以下代码按照时间顺序(2020/5/22 —— 2020/6/25)排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> using namespace std ;const int N = 2e5 +5 ;int a[N];int getans (int n) { int ans = 0 ,x=0 ; for (int i=1 ;i<=n;++i){ x+=a[i]; ans+=x/i; x%=i; } return ans; } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas,n,x; cin >>cas; while (cas--){ cin >>n; for (int i=1 ;i<=n;++i) a[i]=0 ; for (int i=0 ;i<n;++i){ cin >>x; ++a[x]; } cout <<getans(n)<<endl ; } return 0 ; }
1354C :计算包含单位正 $n$ 边形的最小正方形的边长1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <bits/stdc++.h> using namespace std ;const double pi = acos (-1.0 );int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas,n; cin >>cas; cout .precision(10 ); while (cas--){ cin >>n; if (n&1 ){ double x = sqrt (1 -cos ((n/2 )*pi/n)); double y = sqrt (1 -cos ((n/2 +1 )*pi/n)); cout <<(x+y)/2 /sin (pi/2 /n)<<endl ; }else cout <<1.0 /tan (pi/2 /n)<<endl ; } return 0 ; }
1354D :模拟操作: 加数字或删除第 $k$ 小的数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> using namespace std ;const int N = 1e6 + 5 ;int s[N], Size;int lowbit (int n) { return n & (-n); } void add (int id, int p) { while (id <= Size) { s[id] += p; id += lowbit(id); } } int sum (int id) { int r = 0 ; while (id) { r += s[id]; id -= lowbit(id); } return r; } int find (int k) { int l = 0 , r = Size; while (r>l) { int m = (l + r) >> 1 ; if (sum(m) >= k) r = m; else l = m+1 ; } return r; } void del (int k) { add(find (k), -1 ); } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int q, x; while (cin >> Size >> q) { for (int i = 1 ; i <= Size; ++i) s[i] = 0 ; for (int i = 1 ; i <= Size; ++i) { cin >> x; add(x, 1 ); } while (q--) { cin >> x; if (x > 0 ) add(x, 1 ); else { del(-x); } } int ans = find (1 ); cout <<(sum(ans)>0 ?ans:0 )<< endl ; } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <bits/stdc++.h> using namespace std ;const int N = 102 ;int dp[N][N];bool isin[N][N],chose[N];using node = tuple<int ,int ,int >;node q[N]; void getans (int n,int k) { for (int i=1 ;i<=n;++i){ dp[i][0 ] = dp[i-1 ][0 ]+(k-1 )*get <2 >(q[i]); for (int j = 1 ;j<i && j<=k;++j){ int x = dp[i-1 ][j]+(k-1 )*get <2 >(q[i]); int y = dp[i-1 ][j-1 ]+(j-1 )*get <2 >(q[i])+get <1 >(q[i]); if (x>y){ dp[i][j] = x; isin[i][j] = false ; }else { dp[i][j] = y; isin[i][j] = true ; } } if (i<=k){ dp[i][i] = dp[i-1 ][i-1 ]+(i-1 )*get <2 >(q[i])+get <1 >(q[i]); isin[i][i] = true ; } } for (int i=n,j=k;i;--i){ if (isin[i][j]){ chose[i]=true ; --j; }else { chose[i]=false ; } } } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas,n,k,a,b; cin >>cas; while (cas--){ cin >>n>>k; for (int i=1 ;i<=n;++i){ cin >>a>>b; q[i] = {i,a,b}; } sort(q+1 ,q+n+1 ,[](const node & x, const node & y){ return get <2 >(x)<get <2 >(y); }); cout <<(2 *n-k)<<endl ; getans(n,k); int last = 0 ; for (int i=1 ;i<=n;++i){ if (chose[i]){ if (++last == k){ last = i; break ; } cout <<get <0 >(q[i])<<" " ; } } for (int i=1 ;i<=n;++i){ if (!chose[i]) cout <<get <0 >(q[i])<<" " <<-get <0 >(q[i])<<" " ; } cout <<get <0 >(q[last])<<endl ; } return 0 ; }
1345F :经典二分,利用二阶偏导(离散偏导,即增量)是常量1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; LL k,x; while (cin >>n>>k){ vector <LL> q; for (int i=0 ;i<n;++i){ cin >>x; q.push_back(x); } auto get = [](LL x ,LL mx){ LL l=0 ,r=x-1 ; while (l<=r){ LL m=(l+r)>>1 ; if (x-m*(m+1 )*3 == mx) return make_pair(m,m+1 ); if (x-m*(m+1 )*3 > mx) l = m+1 ; else r = m-1 ; } return make_pair(l,l); }; LL l = -3e18 -3e8 , r = 1e9 ; while (l<=r){ LL m = (l+r)>>1 ,x=0 ,y=0 ; for (auto &i:q){ auto tmp = get (i,m); x+=tmp.first; y+=tmp.second; } if (x>k) l = m+1 ; else if (y<k) r = m-1 ; else { y-=k; for (auto &i:q){ auto tmp = get (i,m); if (tmp.first<tmp.second && y-->0 ){ cout <<tmp.first<<" " ; }else { cout <<tmp.second<<" " ; } } cout <<endl ; break ; } } } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> using namespace std ;using LL = long long ; int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas,n,x; cin >>cas; while (cas--){ cin >>n; set <int > q; for (int i=0 ;i<n;++i){ cin >>x; q.insert(x); } cout <<q.size ()<<endl ; } return 0 ; }
1325D :(异或)位运算:判断是否有 $n$ 个数,异或和为 $u$, 和为 $v$1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); LL u,v; while (cin >>u>>v){ if (u>v||((v-u)&1 )){ cout <<-1 <<endl ; }else if (u == v){ if (u) cout <<1 <<endl ; cout <<u<<endl ; }else { v = (v-u)>>1 ; if (u&v) cout <<3 <<endl <<u<<" " <<v<<" " <<v<<endl ; else cout <<2 <<endl <<u+v<<" " <<v<<endl ; } } return 0 ; }
注意到 $a+b = a \wedge b + 2(a \& b)$,并且 $(a \wedge b) \& (a\&b) = 0$ 当且仅当 $a b = 0$
1358D :给定区间最值问题,但是可以写的很漂亮
注意到区间最值一定在左或者右端点达到局部最大值(极值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 4e5 +102 ;int n,n2;LL a[N],b[N],s[N]; LL f (LL x) { return ((x+1 )*x)>>1 ; } LL sumx (LL x) { int it = lower_bound(b,b+n2,x) - b; return s[it-1 ]+f(x-b[it-1 ]); } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); LL d; cin >>n>>d; n2=n<<1 ; for (int i=0 ;i<n;++i){ cin >>a[i]; a[i+n] = a[i]; } b[0 ] = a[0 ];s[0 ] = f(a[0 ]); for (int i=1 ;i<n2;++i){ b[i]=b[i-1 ]+a[i]; s[i]=s[i-1 ]+f(a[i]); } LL ans = 0 ; for (int i=0 ;i<n;++i){ ans = max (ans,sumx(b[i]+d-1 )-s[i]+a[i]); } for (int i=n;i<n2;++i){ ans = max (ans,s[i]-sumx(b[i]-d)); } cout <<ans<<endl ; return 0 ; }
注意到如果 $k$ 满足答案,则 $2k$ 也满足,所以不妨设 $k> \lfloor \frac{n}{2} \rfloor$,另一方面题目中要求若 $i> \lceil \frac{n}{2} \rceil$,则 $a_{i} = x$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 5e5 +5 ;LL a[N],s[N],m[N]; int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; cin >>n; int n2 = (n+1 )>>1 ; for (int i=1 ;i<=n2;++i){ cin >>a[i]; s[i] = s[i-1 ]+a[i]; } LL x; cin >>x; for (int i=n2+1 ;i<=n;++i){ a[i]=x; s[i]=s[i-1 ]+x; } for (int i=1 ;i<=n2;++i){ m[i+1 ] = min (m[i],x*i-s[i]); } for (int k=n2;k<=n;++k){ if (s[k]+m[n-k+1 ]>0 ){ cout <<k<<endl ; return 0 ; } } cout <<-1 <<endl ; return 0 ; }
1359C : 读题被坑…(反正就是倒冷热水接近给定温度)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >>cas; while (cas--){ int h,t,c; cin >>h>>c>>t; h-=c;t-=c; if (h>=2 *t){ cout <<2 <<endl ; }else { LL n = (h-t)/(2 *t-h); auto f = [](LL n,int h,int t){ return (n+1 )*(2 *n+3 )*h+(2 *n+1 )*(n+2 )*h>(2 *n+1 )*(2 *n+3 )*t*2 ; }; if (f(n,h,t)) ++n; cout <<2 *n+1 <<endl ; } } return 0 ; }
然后对于奇数项,单调递减趋于平均问题,然后判断的时候转化成整数的判断
1359D :线段树,但是由于数据特殊,可以不用线段树1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 5e5 +5 ;int a[N];int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(NULL ); int n; cin >>n; for (int i=1 ;i<=n;++i){ cin >>a[i]; } int ans=0 ; for (int m=1 ;m<=30 ;++m){ int suml =0 ,sum=0 ,maxa = -31 ; for (int i=1 ;i<=n;++i){ if (a[i]>m){ sum = suml = 0 ; maxa = -31 ; }else { sum+=a[i]; maxa = max (maxa,a[i]); ans = max (ans,sum-suml-maxa); suml = min (sum,suml); } } } cout <<ans<<endl ; return 0 ; }
注意到 $-30<a_i<30 $
1359E :找到公式后就是模素数组合数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std ;using LL=long long ;const int N = 5e5 +2 ;const LL M = 998244353 ;LL inv[N],frac[N]; LL powmod (LL x, LL n, LL p) { LL r=1 ; while (n) { if (n&1 ) r=r*x%p; n>>=1 ; x=x*x%p; } return r; } void init () { frac[0 ]=inv[0 ]=1 ; for (int i=1 ;i<N;++i){ frac[i] = frac[i-1 ]*i%M; } inv[N-1 ] = powmod(frac[N-1 ],M-2 ,M); for (int i=N-2 ;i;--i){ inv[i] = inv[i+1 ]*(i+1 )%M; } } LL C (int n,int m) { if (n<m) return 0 ; if (n==m||m==0 ) return 1 ; return frac[n]*inv[m]%M*inv[n-m]%M; } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(NULL ); int n,k; cin >>n>>k; init(); LL ans = 0 ; for (int i=1 ;i<=n;++i){ int x = n/i; if (x<k) break ; ans += C(x-1 ,k-1 ); } cout <<ans%M<<endl ; return 0 ; }
先考虑$k=2$ 的情况知道 $a_1|a_2$,然后发现对于任意$k$,仅需 $a_1|a_i$ 即可。
1363E :有根数,互换子树的节点位数到预定值,dfs 和图存储范例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 2e5 +2 ;int a[N];vector <int > g[N];bool b[N],c[N];tuple<int,int,LL> dfs(int u,int parent,int mn){ int l=0 ,r=0 ; LL cost=0 ; if (b[u]!=c[u]){ if (b[u]) ++l; else ++r; } for (auto &v:g[u]){ if (v == parent) continue ; auto [sl,sr,scost] = dfs(v,u,min (mn,a[u])); l+=sl;r+=sr;cost+=scost; } if (a[u]<mn){ int take = min (l,r); cost += LL(a[u])*(take*2 ); l -= take; r -= take; } return {l,r,cost}; } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n,u,v; cin >>n; for (int i=1 ;i<=n;++i){ cin >>a[i]>>b[i]>>c[i]; } for (int i=1 ;i<n;++i){ cin >>u>>v; g[u].push_back(v); g[v].push_back(u); } auto [l,r,cost] = dfs(1 ,0 ,2e9 ); cout <<(l||r?-1 :cost)<<endl ; return 0 ; }
上面代码写得呢,就很优雅,哈哈!
1361E :下面代码至今没过我也是没懂为什么
模仿进制的操作,最后用其他代码过的题。多设几个变量没坏处的其实。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 1e6 +2 ;const LL M = 1e9 +7 ;LL a[N],p; template <typename T,typename U>T powmod (T x,U n,T p) { T r (1 ) ; while (n){ if (n&1 ) r=r*x%p; n>>=1 ; x=x*x%p; } return r; } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas,n; cin >>cas; while (cas--){ cin >>n>>p; for (int i=1 ;i<=n;++i){ cin >>a[i]; } if (p==1 ){ cout <<(n&1 )<<endl ;continue ; } sort(a+1 ,a+n+1 ); LL ans = 1 ; while (n>1 ){ if (ans==0 ){ ans = 1 ; }else { if (a[n]==a[n-1 ]) --ans; else { while (a[n]>a[n-1 ]){ ans*=p; --a[n]; if (ans>n) break ; } if (a[n]!=a[n-1 ]) break ; --ans; } } --n; } ans = ans%M*powmod(p,a[n],M)%M; for (int i=1 ;i<n;++i){ ans-=powmod(p,a[i],M); } ans = (M+ans%M)%M; cout <<ans<<endl ; } return 0 ; }
我们称一个长度为 $n$ 的序列 $p$ 为好序列 ,如果对任意正整数$k>1$,存在 $1 \leq i < j \leq n$ 使得,$p_i = k-1, p_j = k$
存在 好序列 和长为 $n$ 的排列一一对应:
给定排列 $a_1,a_2,\cdots, a_n$ , 相邻两个之间添加 >
或 <
,那么 $p_{a_i}$ 就定义为 $a_1,\cdots,a_i$ 中 <
个数加一
给定好序列 $p$,从右到左依次标记出 1,2,...
,直到标记完所有数即得到了排列
因此 好序列 中最大值对应这排列中单调递减区间的个数!
答案要求的是:所有好序列中出现 $k$ 的个数之和。
代码下次再写吧…
1365E :这题其实没啥,但是比赛的时候竟然把 |
想成了 ^
,很烦1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; while (cin >>n){ LL a[n]; for (int i=0 ;i<n;++i){ cin >>a[i]; } sort(a,a+n,greater<LL>()); LL ans = a[0 ]; for (int i=0 ;i<n;++i){ if (2 *a[i]<=a[0 ]) break ; for (int j=i+1 ;j<n;++j){ LL t = a[i]|a[j]; ans = max (ans,t); for (int k=j+1 ;k<n;++k){ ans = max (ans,t|a[k]); } } } cout <<ans<<endl ; } return 0 ; }
1312D :计算满足条件的数列个数计算满足下列条件的数列个数:
数列项数为 $n$,且每一项都是不超过 $m$ 的正整数,$2\cdot 10^5 =N>m \geq n \geq 2$
数列中有且仅有两项是相同的
数列在 $i$ 项前严格单调递增,$i$ 项后严格单调递减
我们枚举 $i$ 的位置和 $i$ 的值,以及相同的项,则显然有如下计算式
预处理一下阶乘和阶乘逆,我们就可以在 $O(N)$ 解决问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const LL M = 998244353 ;const int N = 2e5 +2 ;LL powmod (LL a,LL n) { LL r (1 ) ; while (n){ if (n&1 ) r=r*a%M; n>>=1 ; a=a*a%M; } return r; } LL fac[N],ifac[N]; void init () { fac[0 ]=1 ; for (int i=1 ;i!=N;++i){ fac[i] = fac[i-1 ]*i%M; } ifac[N-1 ] = powmod(fac[N-1 ],M-2 ); for (int i=N-2 ;~i;--i){ ifac[i] = ifac[i+1 ]*(i+1 )%M; } } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); init(); int n,m; while (cin >>n>>m){ LL x=0 ,y=0 ; for (int i=n-1 ;i<=m;++i){ x+=fac[i-1 ]*ifac[i-n+1 ]%M; } for (int i=2 ;i<n;++i){ y+=ifac[i-2 ]*ifac[n-i-1 ]%M; } cout <<(x%M)*(y%M)%M<<endl ; } return 0 ; }
但是官方题解 给了一个更简单的方法
首先把 $n=2$ 时无解,所以考虑 $n>2$ 的情况,首先把所有用到的数字选好:$m \choose n-1$,然后选择好相同的数字:$n-2$,然后剩下的非最大的数,要么放在最大的数左边要么放在最大的数右边:$2^{n-3}$,即最终答案是 ${m \choose n-1}(n-2)2^{n-3}$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 1022 ;const int inf = 0x3f3f3f3f ;int ans[502 ][N],b[502 ][N]; int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n,x; while (cin >>n){ memset (ans,inf,sizeof (ans)); cin >>x; ans[0 ][x] = 1 ; b[0 ][x]=0 ; for (int i=1 ;i<n;++i){ cin >>x; for (int j=1 ;j<N;++j){ if (ans[i-1 ][j]!=inf){ ans[i][x] = min (ans[i][x],ans[i-1 ][j]+1 ); } b[i][x]=i; } int s = i-1 ; while (s>=0 &&ans[s][x]!=inf){ ans[i][x+1 ] = ans[s][x]; b[i][x+1 ] = b[s][x]; s = b[s][x]-1 ; ++x; } } int r = inf; for (int i=1 ;i<N;++i){ r = min (r,ans[n-1 ][i]); } cout <<r<<endl ; } return 0 ; }
下面代码取自:jiangly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <iostream> #include <algorithm> #include <vector> class SegmentTree {private : int n; std ::vector <long long > min , tag; void add (int p, long long v) { min [p] += v; tag[p] += v; } void push (int p) { add(2 * p, tag[p]); add(2 * p + 1 , tag[p]); tag[p] = 0 ; } void pull (int p) { min [p] = std ::min (min [2 * p], min [2 * p + 1 ]); } void rangeAdd (int p, int l, int r, int x, int y, int v) { if (l >= y || r <= x) return ; if (l >= x && r <= y) return add(p, v); int m = (l + r) / 2 ; push(p); rangeAdd(2 * p, l, m, x, y, v); rangeAdd(2 * p + 1 , m, r, x, y, v); pull(p); } public : SegmentTree(int n) : n(n), min (4 * n), tag(4 * n) {} void rangeAdd (int l, int r, int v) { rangeAdd(1 , 0 , n, l, r, v); } long long getMin () { return min [1 ]; } }; int n;std ::vector <int > p, a, pos;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::cin >> n; p.resize(n); a.resize(n); pos.resize(n); for (int i = 0 ; i < n; ++i) { std ::cin >> p[i]; --p[i]; pos[p[i]] = i; } for (int i = 0 ; i < n; ++i) std ::cin >> a[i]; SegmentTree t (n - 1 ) ; for (int i = 0 ; i < n; ++i) t.rangeAdd(pos[i], n - 1 , a[pos[i]]); long long ans = t.getMin(); for (int i = 0 ; i < n; ++i) { t.rangeAdd(pos[i], n - 1 , -a[pos[i]]); t.rangeAdd(0 , pos[i], a[pos[i]]); ans = std ::min (ans, t.getMin()); } std ::cout << ans << "\n" ; return 0 ; }
1295F :给定区间的非降序列概率(个数)
下面代码取自:jiangly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <iostream> #include <algorithm> #include <vector> constexpr int P = 998'244'353 ;int power (int a, int b) { int res = 1 ; while (b > 0 ) { if (b & 1 ) res = 1L L * res * a % P; a = 1L L * a * a % P; b >>= 1 ; } return res; } int n;std ::vector <int > l, r, v, inv, dp;std ::vector <std ::vector <int >> c;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); std ::cin >> n; l.resize(n); r.resize(n); v.reserve(2 * n); inv.resize(n + 1 ); for (int i = 1 ; i <= n; ++i) inv[i] = power(i, P - 2 ); int total = 1 ; for (int i = 0 ; i < n; ++i) { std ::cin >> l[i] >> r[i]; ++r[i]; v.push_back(l[i]); v.push_back(r[i]); total = 1L L * total * (r[i] - l[i]) % P; } std ::sort(v.begin (), v.end ()); v.erase(std ::unique(v.begin (), v.end ()), v.end ()); for (int i = 0 ; i < n; ++i) { l[i] = std ::lower_bound(v.begin (), v.end (), l[i]) - v.begin (); r[i] = std ::lower_bound(v.begin (), v.end (), r[i]) - v.begin (); } c.assign(v.size () - 1 , std ::vector <int >(n + 1 )); for (int i = 0 ; i < int (v.size ()) - 1 ; ++i) { c[i][0 ] = 1 ; for (int j = 1 ; j <= n; ++j) c[i][j] = 1L L * c[i][j - 1 ] * (j - 1 + v[i + 1 ] - v[i]) % P * inv[j] % P; } dp.resize(n + 1 ); dp[0 ] = 1 ; for (int i = int (v.size ()) - 1 ; i >= 0 ; --i) { for (int a = n - 1 ; a >= 0 ; --a) { for (int b = a; b < n; ++b) { if (i < l[b] || i >= r[b]) break ; dp[b + 1 ] = (dp[b + 1 ] + 1L L * dp[a] * c[i][b - a + 1 ]) % P; } } } std ::cout << 1L L * dp[n] * power(total, P - 2 ) % P << "\n" ; return 0 ; }
1364C :已知 $a_i=MEX({b_1,\cdots,b_i})$ 为不出现在 $b_1,\cdots,b_i$ 中的最小非负整数,在给定 $0\leq a_i \leq i,a_i<a_{i+1}$ 的情况下,给出一种 $b_i$ 的方案方案就是:设当前最大值为 $n$,从尾部开始,先标 a[i]
的值被访问,然后看 a[i]
是否等于 a[i-1]
,如果是就在没被访问的点中给一个最大给 b[i]
,否则 $b[i]=a[i-1]$。然后边界判断! i=1
时,当作不等处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 1e5 +2 ;int a[N],b[N];bool v[N];int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; cin >>n; for (int i=1 ;i<=n;++i){ cin >>a[i]; } int ma = n; for (int i=n;i>0 ;--i){ v[a[i]]=1 ; if (i==1 ||a[i]==a[i-1 ]){ while (ma>=0 &&v[ma]) --ma; v[ma] = true ; b[i] = ma--; }else { b[i]=a[i-1 ]; v[b[i]]=1 ; } } for (int i=1 ;i<=n;++i){ cout <<b[i]<<" " ; } cout <<endl ; return 0 ; }
1353D :有趣的标准优先队列+BFS 题目
初始值为 0 的长度为 n 的数组,每次在连续 0 中长度最大的那一段的中点编号,直到编完。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std ;using LL = long long ;struct Node { int l,r; bool operator <(const Node & A)const { return (r-l)<(A.r-A.l)||((r-l)==(A.r-A.l)&&l>A.l); } Node(int _l,int _r){ l = _l; r = _r; } }; int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >>cas; while (cas--){ LL n; cin >>n; int a[n+1 ]; priority_queue<Node> q; q.push({1 ,n}); int now=0 ; while (!q.empty()){ auto u = q.top(); int l=u.l,r=u.r; q.pop(); a[(l+r)/2 ] = ++now; if (l==r) continue ; if ((r-l)%2 ==0 ){ q.push(Node(l,(l+r)/2 -1 )); q.push(Node((l+r)/2 +1 ,r)); }else { q.push(Node((l+r)/2 +1 ,r)); if (l<(l+r)/2 ) q.push(Node(l,(l+r)/2 -1 )); } } for (int i=1 ;i<=n;++i){ cout <<a[i]<<" \n" [i==n]; } } return 0 ; }
1353E :在 01
序列中改变最小的位,使得其中的所有 1
都是连续的。
设 dp[i]
为使得前 i
位中 1 连续出现,且第 i
位为 1 的最小改变位数,状态转移就显然了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >>cas; while (cas--){ int n,k; string a; cin >>n>>k>>a; auto solve = [](const string &s){ int n = s.length(); int all = count(s.begin (),s.end (),'1' ); int s1=(s[0 ]=='1' ); int ans = all-s1,res = 1 -s1; for (int i=1 ;i<n;++i){ int cur = (s[i]=='1' ); s1+=cur; res = 1 -cur+min (res,s1-cur); ans = min (ans,res+all-s1); } return all-ans; }; int mx=0 ; for (int i=0 ;i<k;++i){ string s; for (int j=i;j<n;j+=k){ s+=a[j]; } mx = max (mx,solve(s)); } cout <<count(a.begin (),a.end (),'1' )-mx<<endl ; } return 0 ; }
1367E :不难但是很有趣的数学题
在给定一些元素中取出最大数量构成一个 k 旋转不变的圈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std ;using LL = long long ; int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >>cas; while (cas--){ int n,k; string s; cin >>n>>k>>s; int c[26 ]={0 }; for (auto &i:s) ++c[i-'a' ]; sort(c,c+26 ,greater<int >()); auto check=[&](int m){ int d = __gcd(m,k); int md = m/d; int x = 0 ; for (int i=0 ;i<26 ;++i){ x+=c[i]/md; } return x>=d; }; while (!check(n)) --n; cout <<n<<endl ; } return 0 ; }
1367F :给定规则最小步骤使得数列有序
只允许将数列中某一个数最前或者最后,问最少多少步使得数列有序。
我们称数列中两个数 $a_i \leq a_j$是相接 的,如果 $i<j$ 且排好序后 $a_i,a_j$ 相邻。那么我们的答案就是 $n-$ 最长相接子列的长度。
原因 :首先它是一个可行的答案,其次答案的方案去掉数列中被移动的数,剩下的数必然是相接的!
因为数列中的数的大小并不影响结果,影响结果的是相对关系,因此可以通过预处理,让数列的取值范围是一个区间。
记 dp[i]
为以 $i$ 结尾的最长相接子列。状态转移是好写的。
如果数列中元素两两互异,会很简单,因为有相同元素的时候要考虑 0 1 0 2
这种数列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std ;using LL = long long ; int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int cas; cin >>cas; while (cas--){ int n; cin >>n; int a[n],id[n]; for (int i=0 ;i<n;++i) cin >>a[i]; iota(id,id+n,0 ); sort(id,id+n,[&](int &i,int &j){ return a[i]==a[j]?i<j:a[i]<a[j]; }); for (int i=0 ;i<n;++i){ a[id[i]] = i; } int dp[n],ans=0 ; for (int i =0 ;i<n;++i){ dp[i] = 1 ; if (a[i]>0 && id[a[i]-1 ]<i) dp[i] += dp[id[a[i]-1 ]]; ans = max (ans,dp[i]); } cout <<n-ans<<endl ; } return 0 ; }
否则,我们就要考虑 $i$ 之前 a[i]
严格小的相接元的 dp
最小值加上这些元的元素个数。
但是也可以直接求不用 DP 参考 jiangly 的代码 ,直接找包含第 $i$ 位最长的相接子列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> constexpr int N = 2e5 ;int a[N], p[N];int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int z; std ::cin >> z; while (z--) { int n; std ::cin >> n; for (int i = 0 ; i < n; ++i) std ::cin >> a[i]; std ::iota(p, p + n, 0 ); std ::sort(p, p + n, [&](int i, int j) {return a[i] < a[j] || (a[i] == a[j] && i < j);}); int ans = 0 ; for (int l = 0 , r; l < n; l = r) { for (r = l + 1 ; r < n && p[r] > p[r - 1 ]; ++r) ; int res = r - l; if (l) for (int i = l - 1 ; i >= 0 && a[p[i]] == a[p[l - 1 ]]; --i) if (p[i] < p[l]) ++res; if (r < n) for (int i = r; i < n && a[p[i]] == a[p[r]]; ++i) if (p[i] > p[r - 1 ]) ++res; ans = std ::max (ans, res); } for (int l = 0 , m, r; l < n; l = m) { for (m = l; m < n && a[p[m]] == a[p[l]]; ++m) ; if (m == n) break ; for (r = m; r < n && a[p[r]] == a[p[m]]; ++r) ; for (int i = l, j = m; i < m; ++i) { while (j < r && p[j] < p[i]) ++j; ans = std ::max (ans, i + 1 - l + r - j); } } std ::cout << n - ans << "\n" ; } return 0 ; }
1368B :简单的问题比赛的时候无语的想复杂了!然后就是参看 tourist 男神的优雅代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> using namespace std ; int main () { ios::sync_with_stdio(false ); cin .tie(0 ); long long k; cin >> k; string s = "codeforces" ; int n = (int ) s.size (); vector <long long > a (n, 1 ) ; long long prod = 1 ; for (int it = 0 ; prod < k; it = (it + 1 ) % n) { prod = prod / a[it] * (a[it] + 1 ); ++a[it]; } for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < a[i]; j++) { cout << s[i]; } } cout << '\n' ; return 0 ; }
1368D :一眼看出!哎要不是 B 题被卡了,我决定能上大分!好气1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; while (cin >>n){ int a[20 ]={0 },x; for (int i=0 ;i<n;++i){ cin >>x; int j=0 ; while (x){ if (x&1 ) ++a[j]; x>>=1 ; ++j; } } LL r=0 ; for (int i=0 ;i<n;++i){ LL t = 0 ; for (int i=0 ,j=1 ;i<20 ;++i,j<<=1 ){ if (a[i]){ t+=j; --a[i]; } } r+=t*t; } cout <<r<<endl ; } return 0 ; }
1285D :比赛的时候想到了,但是怕复杂度过补了,能过复杂度是因为每个元素 $a$ 最多递归 $\log a$ 次1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std ;using LL = long long ;LL solve (vector <int > &a,int bit ) { if (bit <0 ) return 0 ; vector <int > l,r; for (auto &x:a){ if ((x>>bit )&1 ) l.push_back(x); else r.push_back(x); } if (l.size ()==0 ) return solve(r,bit -1 ); if (r.size ()==0 ) return solve(l,bit -1 ); return min (solve(l,bit -1 ),solve(r,bit -1 ))+(1 <<bit ); } int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; while (cin >>n){ vector <int > a (n) ; for (auto &x:a) cin >>x; cout <<solve(a,30 )<<endl ; } return 0 ; }
1285C :找出使得$\max(a,b)$最小并使得$lcm(a,b)=x$的最小$a,b$
将 $x$ 分解素因子可知,$\max(a,b)$ 最小的前提是,$\gcd(a,b)=1$,又因为 $x$ 的因子个数不超过$2\sqrt{x}$,搞定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); LL n; while (cin >>n){ LL r = 1 ; for (LL i=1 ;i*i<=n;++i){ if (n%i==0 &&__gcd(i,n/i)==1 ){ r=i; } } cout <<r<<" " <<n/r<<endl ; } return 0 ; }
本来不想写这个题,但是由于下题都是 lcm
问题,又是同一场,所以就记录一下
1285F :一道很秀的 lcm 题,计算 $\displaystyle \max_{1\leq i< j \leq n} lcm(a_i,a_j)$
参考这里:https://www.xht37.com/codeforces-round-613-div-2-%E9%A2%98%E8%A7%A3/#Classical
通过加入 $a_i$ 的所有因子,我们可以改成计算 $\displaystyle \max_{\gcd(a_i,a_j)=1} a_ia_j$,我们将 $a_i$ 从大到小排序,然后开始遍历,用堆 s 保存之前的内容,注意到,如果堆 s 中有一个元素 t,跟当前需要遍历的元素 $a_i$ 互素,那么小于 s 中小于 $t$ 的元素讲不再能为结果做贡献。因此可以踢出栈中,现在问题是我们如何快速的记录堆 $s$ 中是否有与$a_i$ 互素的元素 。记 $c_i$ 为堆中是 $i$ 的倍数的元素个数,那么堆中和$x$互素的个数为 $cop = \sum_{d|x} \mu(d) c_d$,这是因为包容排斥原理,首先所有数都是 1 的倍数,然后减去和 $x$ 的最小公约数为素数的,在加上和 $x$ 的最小公约数为两个素数相乘…。若 $cop$ 不为 0,那么就一个个的剔除,剔除的时候可以计算一下乘积(虽然不一定互素,但是不会影响的最终结果)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <bits/stdc++.h> using namespace std ;using LL = long long ;const int N = 1e5 +2 ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; while (cin >>n){ bool a[N]={}; int mx=0 ; for (int i=0 ,x;i<n;++i){ cin >>x; a[x]=1 ; mx = max (mx,x); } vector <int > p[mx+1 ]; int mu[mx+1 ]={},c[mx+1 ]={}; mu[1 ] = 1 ; for (int i=1 ;i<=mx;++i){ p[i].push_back(i); for (int j=2 *i;j<=mx;j+=i){ p[j].push_back(i); a[i]|=a[j]; mu[j]-=mu[i]; } } stack <int > s; LL ans = mx; for (int i=mx;i;--i){ if (a[i]){ int cop = 0 ; for (auto &x:p[i]) cop+=mu[x]*c[x]; while (cop){ for (auto &x:p[s.top()]){ --c[x]; if (i%x==0 ) cop-=mu[x]; } ans = max (ans,LL(i)*s.top()); s.pop(); } for (auto &x:p[i]) ++c[x]; s.push(i); } } cout <<ans<<endl ; } return 0 ; }
1370D :在长为 n 的序列中找一个长为 k 的子列,子列中奇数项最大值和偶数项最大值的最小值最小
比赛的时候,我很快就知可以二分查找答案,但是顾前不顾尾,下面代码没过,因为当最后一个数正好是满足 a[i]<=m
时答案就不行了!应该按照原来的逻辑去叠加啊!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n,k; cin >>n>>k; int a[n],r=0 ,l=1e9 +2 ; for (int i=0 ;i<n;++i){ cin >>a[i]; r = max (r,a[i]); l = min (l,a[i]); } auto f = [&](int m){ int now = -2 ,s=0 ; for (int i=k%2 ;i<n;++i){ if (a[i]<=m){ if (i>now+1 ){ ++s; now = i; } } } return s>=k/2 ; }; while (l<=r){ int m = (l+r)/2 ; if (f(m)) r=m-1 ; else l=m+1 ; } cout <<l<<endl ; return 0 ; }
下面是修正的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n,k; cin >>n>>k; vector <int > a (n) ; for (auto &x:a) cin >>x; auto g = [&](int m,bool cur){ int s = 0 ; for (int i=0 ;i<n;++i){ if (cur){ cur = !cur; ++s; }else { if (a[i]<=m){ ++s; cur = !cur; } } } return s>=k; }; int l = *min_element(a.begin (),a.end ()); int r = *max_element(a.begin (),a.end ()); while (l<=r){ int m = (l+r)/2 ; if (g(m,0 )||g(m,1 )) r=m-1 ; else l=m+1 ; } cout <<l<<endl ; return 0 ; }
1370E :找出最少的轮换使得字符串 s 变成 t我一开始知道是贪心,然后我以为 是找到其中最长的连续 0 或者 1 ,然后发现并不是!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; string a,b; cin >>n>>a>>b; int ab=0 ; for (auto &x:a) if (x=='1' ) ++ab; for (auto &x:b) if (x=='1' ) --ab; if (ab){ cout <<-1 <<endl ;return 0 ; } string s; for (int i=0 ;i<n;++i){ if (a[i]!=b[i]) s.push_back(a[i]); } s+=s; int ans = 0 ; for (int i=0 ,t;i<s.size ();++i){ if ((i == 0 )||(s[i]!=s[i-1 ])) t=0 ; ans = max (ans,++t); } cout <<ans<<endl ; return 0 ; }
然后看了官方题解 !
在有解的前提下,我们可以构造取值在 $\{-1,0,1\}$ 的数组 $a$: if $s_i = t_i,a_i = 0$,else if $s_i = 1,a_i=1$ else $a_i=-1$。因此答案就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; string s,t; cin >>n>>s>>t; vector <int > a; for (int i=0 ;i<n;++i){ if (s[i]==t[i]) continue ; a.emplace_back(s[i]=='1' ?1 :-1 ); } if (accumulate(a.begin (),a.end (),0 )){ cout <<-1 <<endl ; return 0 ; } auto f = [&](int sign){ int mx = 0 ,cur=0 ; for (auto &x:a){ cur += sign*x; mx = max (mx,cur); cur = max (cur,0 ); } return mx; }; cout <<max (f(1 ),f(-1 ))<<endl ; return 0 ; }
1263D :Set<int>
集合操作范例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); int n; cin >>n; set <int > a[26 ]; for (int i=0 ;i<n;++i){ string s; cin >>s; for (auto &c:s) a[c-'a' ].insert(i); } set <set <int >> q; int x=0 ; while (a[x].size ()==0 ) ++x; q.insert(a[x]); for (int i=x+1 ;i<26 ;++i){ if (a[i].size ()==0 ) continue ; set <set <int >> p; for (auto &x:q){ set <int > t; set_intersection(x.begin (),x.end (),a[i].begin (),a[i].end (),inserter(t, t.begin ())); if (t.size ()){ set_union(x.begin (),x.end (),a[i].begin (),a[i].end (),inserter(t, t.begin ())); a[i] = t; p.insert(x); } } set <set <int >> t; set_difference(q.begin (),q.end (),p.begin (),p.end (),inserter(t, t.begin ())); t.insert(a[i]); q = t; } cout <<q.size ()<<endl ; return 0 ; }
题解写在官方的 教程里面 ,代码也不想 copy 过来了。
补充一个更好的生成函数的做法:已知 $a_n$,满足 $a_n = a_{n-1} + 2 a_{n-2} + (n \% 3==0?4:0)$,$a_0 = a_1= a_2 = 0$,计算$a_n$。
设 $f(x) = \sum_{n=0} ^ {\infty} a_n x^n$,则我们有
所以
由于 $\frac{1}{1-x} = \sum_{n=0}^{\infty} x^n$,所以 $\frac{1}{(1-2x)(1+x)} = \frac{2}{3(1-2x)} + \frac{1}{3(1+x)} = \sum_{n=0}^{\infty} \frac{2^{n+1}+(-1)^n}{3}x^n$(推荐),$\frac{1}{(1-2x)(1+x)} = \sum_{n=0}^{\infty} (2x)^n \sum_{n=0}^{\infty}(-x)^n = \sum_{n=0}^{\infty} \frac{2^{n+1}+(-1)^n}{3}x^n$(不推荐),因此
给定两个正整数 $s \leq t$,$A,B$ 两人依次玩游戏,每次可以从$s$变成 $s+1$ 或 $2s$,谁先严格大于 $t$,谁输了。
现在给你一堆的 $s_i,t_i$,$A,B$ 两人依次玩游戏,每一局输了的人作为下一局开始的人。问 $A$ 能否最后一局必赢,能否最后一局必输
先考虑单个 $s,t$ 的情况。
用$f(s,t)$ 分别表示$A$ 是否有必胜策略。
$t$ 为奇数,若 $s$ 为偶数,那么 $A$ 就让它变成 $s+1$,那么无论 $B$ 如何操作只能变成偶数,所以此时 $f(s,t) = 1$,若 $s$ 为奇数,显然若 $B$ 用 $A$ 刚刚的策略,则 $A$ 输,$f(s,t) = 0$。
$t$ 为偶数,若 $2s>t$,此时只能做加法,$f(s,t) = s \mod 2$,若 $4s>t \geq 2s$,此时 $f(s,t)=1$ ,因为总可以变成 $f(2s,t)=0$。若 $4s \leq t$,若 $f(s,\lfloor \frac{t}{4} \rfloor) = 1$, 则 $A$ 必有策略到达区间 $(\lfloor \frac{t}{4} \rfloor,2\lfloor \frac{t}{4} \rfloor)$ 的某一点,从而 $f(s,t)=1$, 同理若 $f(s,\lfloor \frac{t}{4} \rfloor) = 0$,则 $B$ 有 $A$ 的策略,从而 $f(s,t)=0$ (比赛的时候能想出来的人是真的虎 ) 。
用 $g(s,t)$ 分别表示 $A$ 是否有必输的策略:若 $2s>t$,则 $g(s,t)=1$,同理,若 $2s \leq t$,$f(s,\lfloor \frac{t}{2} \rfloor)=1$,则必有策略使得 $B$ 到达 $(\lfloor \frac{t}{2} \rfloor,2\lfloor \frac{t}{2} \rfloor)$ 的某一点,所以 $g(s,t) = f(s,\lfloor \frac{t}{2} \rfloor)$
有了这两个函数后,我们就可以从后往前依次来决定最后能否有必胜或必输的决策。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <bits/stdc++.h> using namespace std ;using LL = long long ;int main () { std ::ios::sync_with_stdio(false );std ::cin .tie(nullptr ); function<bool (LL,LL)> f = [&](LL s,LL t)->bool { if (t%2 ==1 ) return s%2 ==0 ; if (2 *s>t) return s%2 ==1 ; if (4 *s>t) return 1 ; return f(s,t/4 ); }; function<bool (LL,LL)> g = [&](LL s,LL t)->bool { if (2 *s>t) return true ; return f(s,t/2 ); }; int n; cin >>n; bool win=0 ,lose=1 ; for (int i=0 ;i<n;++i){ LL s,t; cin >>s>>t; if (win^lose==0 ) continue ; if (lose){ win = f(s,t); lose = g(s,t); }else { win = !f(s,t); lose = !g(s,t); } } cout <<win<<" " <<lose<<endl ; return 0 ; }
我们可以认为一开始仅有必输策略所以让$A$先选,然后如果$A$原来有必胜且有必败策略,这后面必然一直有,若没有必胜也没有必败策略,则后面也是。所以只用考虑仅有其中之一的情形。
罗列一下自己有兴趣的题目,官方题解
A. Airplane Cliques 题意:给定 $n$ 个节点的树,称两个节点是友好的,如果它们相连的边数不超过 $x$,问对任意 $1 \leq k \leq n$,有多少个元素个数为 $k$ 的集合,它们的点之间两两友好。其中 $1 \leq n \leq 300,000, 0 \leq x < n$,最终答案对 998244353 取模
看别人代码 200 多行,下次吧。
C. Cells Blocking 题意:从 (1, 1)
到 (n, m)
只能往右或往下走格子,然后有些格子是不能走的,让你将 2 个能走的格子变得不能走,最后使得格子 (1, 1)
无法到达 (n, m)
,问有多少种变的方式。 $1 \leq m, n \leq 3000$
做法,首先从任意点看它是否可以到 (n, m)
,再看任意点是否可从 (1, 1)
到。然后就可以选择最下和最上的两个路径,显然路径重叠的点被移除的时候 (1, 1)
就无法到达 (n, m)
(如果一开始就无法到达,答案自然就是 $\text{nm} \choose 2$, nm 表示可以走的格子数),然后最下的路径不重叠的点,必然可以必须需要删去其中的一个,删除之后再让它走最下的路径,看它与最上路径的交点,交点个数就是可选点的个数。(一个小技巧就是一开始重叠的点标记一下,不再考虑,这样就不会重复计数了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const LL M = 998244353 ; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, m; std ::cin >> n >> m; std ::vector <std ::string > s (n) ; int nm = 0 ; for (auto &x : s) { std ::cin >> x; nm += std ::count(x.begin (), x.end (), '.' ); } std ::vector <std ::vector <int >> a (n, std ::vector <int >(m)) ; auto b = a; std ::queue <pii> Q; if (s[n - 1 ][m - 1 ] == '.' ) { Q.push({n - 1 , m - 1 }); b[n - 1 ][m - 1 ] = 1 ; } while (!Q.empty()) { auto [x, y] = Q.front(); Q.pop(); if (x > 0 && s[x - 1 ][y] == '.' && b[x - 1 ][y] == 0 ) { Q.push({x - 1 , y}); b[x - 1 ][y] = 1 ; } if (y > 0 && s[x][y - 1 ] == '.' && b[x][y - 1 ] == 0 ) { Q.push({x, y - 1 }); b[x][y - 1 ] = 1 ; } } if (b[0 ][0 ] == 0 ) { print (1l l * nm * (nm - 1 ) / 2 ); return 0 ; } if (s[0 ][0 ] == '.' ) { Q.push({0 , 0 }); a[0 ][0 ] = 1 ; } while (!Q.empty()) { auto [x, y] = Q.front(); Q.pop(); if (x + 1 < n && s[x + 1 ][y] == '.' && a[x + 1 ][y] == 0 ) { Q.push({x + 1 , y}); a[x + 1 ][y] = 1 ; } if (y + 1 < m && s[x][y + 1 ] == '.' && a[x][y + 1 ] == 0 ) { Q.push({x, y + 1 }); a[x][y + 1 ] = 1 ; } } std ::vector <pii> leftRoad, rightRoad; leftRoad.push_back({0 , 0 }); while (leftRoad.back() != std ::make_pair(n - 1 , m - 1 )) { auto [x, y] = leftRoad.back(); if (x + 1 < n && b[x + 1 ][y]) leftRoad.push_back({x + 1 , y}); else leftRoad.push_back({x, y + 1 }); } rightRoad.push_back({0 , 0 }); while (rightRoad.back() != std ::make_pair(n - 1 , m - 1 )) { auto [x, y] = rightRoad.back(); if (y + 1 < m && b[x][y + 1 ]) rightRoad.push_back({x, y + 1 }); else rightRoad.push_back({x + 1 , y}); } std ::vector <int > cnt (n + m - 1 ) ; int nm1 = 0 ; for (int i = 0 ; i != rightRoad.size (); ++i) { if (leftRoad[i] == rightRoad[i]) ++nm1; else cnt[i] = 1 ; } LL r = 1l l * nm1 * (nm - nm1) + 1l l * nm1 * (nm1 - 1 ) / 2 ; auto f = [&](pii p) { auto [x, y] = p; int r = cnt[x + y] * (rightRoad[x + y] == p); while (x + y) { if (y > 0 && a[x][y - 1 ]) { r += cnt[x + y - 1 ] * (rightRoad[x + y - 1 ] == std ::make_pair(x, y - 1 )); --y; } else { r += cnt[x - 1 + y] * (rightRoad[x - 1 + y] == std ::make_pair(x - 1 , y)); --x; } } x = p.first; y = p.second; while (x + y < n + m - 1 ) { if ((x + 1 < n) && b[x + 1 ][y]) { r += cnt[x + 1 + y] * (rightRoad[x + 1 + y] == std ::make_pair(x + 1 , y)); ++x; } else { r += cnt[x + y + 1 ] * (rightRoad[x + y + 1 ] == std ::make_pair(x, y + 1 )); ++y; } } return r; }; for (int i = 0 ; i < n + m - 1 ; ++i) if (leftRoad[i] != rightRoad[i]) { auto [x, y] = leftRoad[i]; --x; ++y; while (a[x][y] == 0 || b[x][y] == 0 ) { --x; ++y; } r += f({x, y}); } print (r); return 0 ; }
SG 函数-博弈问题
题意:有 $n$ 堆石子 $(a_1, \cdots, a_n)$($a_i \leq n$),每次最少选 1 个,最多选 $x$ 个,最后谁没法选谁输,问对于所有的 $1 \leq x \leq n$,先手赢还是先手输。其中 $1 \leq n \leq 500,000$
首先,对每一个具体的 $x$ 答案是 $f(x) = (a_1 \mod x + 1) \oplus \cdots \oplus (a_n \mod x + 1)$ 是否为 0. 原因可参考之前的博文: SG 函数之取石子博弈
为了优化时间复杂度,我们逐位计算 $f(x)$(即 计算 $f(x) \And 2^{j}$ 的值),首先我们对 $a_i$ 去重,用 $c_i = |\{t | a_t = i \}|$,令 $y = x + 1$。所以此时 $f(x) \And 2^{j} = \sum_{i = 1}^n c[i] (i \mod y) \mod 2 \cdot 2^{j}$ 我们将 $[0, n]$ 分成 $[0, y), [y, 2y), \cdots, [ky, n]$,注意到若 $ky \leq a_i < (k + 1) y$,那么 $a_i \equiv a_i - ky \mod y$,如果对于每个区间我们在预处理的前提下,能在 $O(1)$ 时间复杂度计算出每个区间的值,那么我们就能在 $O(n \log^2 n)$ 解决此问题。注意到区间 $[ky, (k+1)y)$ 对答案的贡献是:那些满足 $ky \leq t < (k+1)y$,且 $t - ky$ 包含 j 位的那些 $c_t$ 的和。我们定义 $\displaystyle f_{i, j} = \sum_{x \geq i, (x - i) \And (2^j) \neq 0} c_x$,那么由于 $(x - i) \And 2^{j} \neq 0$ 等价于 $(x - i \pm 2^{j+1} \neq 0)$。所以有状态转移:
然后区间 $[ky, (k+1)y)$ 对答案的贡献就是 $f_{ky, j} - f_{t, j} - \sum_{i = \min((k + 1)y, t - 2^{j})}^{t - 1} c_i$,其中 $t = ky + \lfloor \frac{y}{2^{j + 1}} \rfloor$ 是不小于 $(k + 1) y$ 且与 $ky$ 模 $2^{j + 1}$ 次数最小的值。后面那个求和是因为那部分的和中 $i - ky$ 必然有 $j$ 位。搞定!
下面代码并不完全和上面解释一致,是因为我们值关心每一个位置是否为 0,用加减不方便,直接异或就行,也就是说其实下面很多 int
都可以换成 bool
,不过 std::vector<bool>
不是 bool 型的向量,用起来不方便就没这么做了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n; std ::cin >> n; std::vector<int> c(n + 1), s(n + 1); for (int i = 0 , x; i < n; ++i) { std ::cin >> x; c[x] ^= 1 ; } s[n] = c[n]; for (int i = n - 1 ; i >= 0 ; --i) s[i] = s[i + 1 ] ^ c[i]; auto sum = [&](int x) { return x > n ? 0 : s[x]; }; std ::vector <int > r (n) ; for (int bit = 0 ; (1 << bit ) <= n; ++bit ) { std ::vector <int > f (n + 1 ) ; auto fum = [&](int x) { return x > n ? 0 : f[x]; }; for (int i = n; i >= 0 ; --i) { f[i] = fum(i + (2 << bit )) ^ sum(i + (1 << bit )) ^ sum(i + (2 << bit )); } for (int x = 0 , y = 2 ; x < n; ++x, ++y) if (r[x] == 0 && y > (1 << bit )) { int sy = 0 , step = (y + (2 << bit ) - 1 ) >> bit + 1 ; for (int k = 0 ; k * y <= n; ++k) { int t = k * y + (step << bit + 1 ); sy ^= f[k * y] ^ fum(t) ^ sum(std ::max ((k + 1 ) * y, t - (1 << bit ))) ^ sum(t); } r[x] |= sy; } } for (auto x : r) std ::cout << (x ? "Alice " : "Bob " ); println ; return 0 ; }
I. Ignore Submasks
本场最简单的一题
题意:给定序列 $a_1, \cdots, a_n$ ($0 \leq a_i < 2^k$),记 $f(x)$ 为使得 $a_i \And x \neq a_i$ 的最小的 $i$,如果没有就为 0,求 $\sum_{i = 0}^{2^k - 1} f(i)$,对答案取模 998244353,其中 $1 \leq n \leq 100, 1 \leq k \leq 60$
做法:相当于一开始有 $k$ 个位是自由的,假设当前还有 $r$ 个位是自由的,$a_i$ 占有其中 c 个自由位,然后其它的自由位随便取,所以它对结果的贡献就是 $(2^r - 1) \cdot 2^{k - r} \cdot i$,注意 $k \leq 60$,很多地方记得加 ll
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl #define print(x) std::cout << (x) << std::endl #define println std::cout << std::endl using LL = long long ;using pii = std ::pair<int , int >;using pll = std ::pair<LL, LL>;const LL M = 998244353 ; int main () { std ::ios::sync_with_stdio(false ); std ::cin .tie(nullptr ); int n, k; std ::cin >> n >> k; std ::vector <LL> a (n) ; for (auto &x : a) std ::cin >> x; std ::vector <bool > v (k, true ) ; LL r = 0 , id = 0 , rest = k; for (auto x : a) { int cnt = 0 ; for (int i = 0 ; i < k; ++i) { if ((x & (1l l << i)) && v[i]) { v[i] = false ; ++cnt; } } r += ((1l l << cnt) - 1 ) % M * ((1l l << (rest -= cnt)) % M) % M * (++id) % M; } print (r % M); return 0 ; }
耻辱记录
记录
明显进步
整理出供自己使用的个人库,并不断更新 2021-01-11
整理出多项式模板,掌握生成函数技术 2021-01-15
学习各种图论算法 2021-01-19
学习树上各种算法 2021-01-24
学习动态规划的各种优化 2021-02-04
学习字符串算法 2021-02-12
学习分治和分块算法 2021-03-16
学习可持续算法 2021-04-28
等一个大赛前的全面复习!外加精准快速的键盘敲击,那技术上更上一层楼,量变引起质变,成为 Grandmaster 就指日可待了