1943+1944.Codeforces Round 934 (Div. 1,Div. 2) - sol
20240321
终于差不多把 Div1 补完了(F 当然没补),第一次打 Div 1,还是出了一些小状况的。唉。
没有补 Div 1 F 的逆天题,选择放弃。
Dashboard - Codeforces Round 934 (Div. 2) - Codeforces
Dashboard - Codeforces Round 934 (Div. 1) - Codeforces
2A. Destroying Bridges
There are \(n\) islands, numbered \(1, 2, \ldots, n\). Initially, every pair of islands is connected by a bridge. Hence, there are a total of \(\frac{n (n - 1)}{2}\) bridges.
Everule lives on island \(1\) and enjoys visiting the other islands using bridges. Dominater has the power to destroy at most \(k\) bridges to minimize the number of islands that Everule can reach using (possibly multiple) bridges.
Find the minimum number of islands (including island \(1\)) that Everule can visit if Dominater destroys bridges optimally.
\(1 \le n \le 100\)。
答案只有可能两种:\(1\) 和 \(n\)。
然而只有当能删的边数 \(\ge n-1\) 时才能为 \(1\),于是你做完了。代码。
2B. Equal XOR
You are given an array \(a\) of length \(2n\), consisting of each integer from \(1\) to \(n\) exactly twice.
You are also given an integer \(k\) ($1 \leq k \leq \lfloor \frac{n}{2} \rfloor $ ).
You need to find two arrays \(l\) and \(r\) each of length \(\mathbf{2k}\) such that:
- \(l\) is a subset\(^\dagger\) of \([a_1, a_2, \ldots a_n]\)
- \(r\) is a subset of \([a_{n+1}, a_{n+2}, \ldots a_{2n}]\)
- bitwise XOR of elements of \(l\) is equal to the bitwise XOR of elements of \(r\); in other words, \(l_1 \oplus l_2 \oplus \ldots \oplus l_{2k} = r_1 \oplus r_2 \oplus \ldots \oplus r_{2k}\)
It can be proved that at least one pair of \(l\) and \(r\) always exists. If there are multiple solutions, you may output any one of them.
\(^\dagger\) A sequence \(x\) is a subset of a sequence \(y\) if \(x\) can be obtained by deleting several (possibly none or all) elements of \(y\) and rearranging the elements in any order. For example, \([3,1,2,1]\), \([1, 2, 3]\), \([1, 1]\) and \([3, 2]\) are subsets of \([1, 1, 2, 3]\) but \([4]\) and \([2, 2]\) are not subsets of \([1, 1, 2, 3]\).
\(2 \le n \le 5 \times 10^4\)。
容易发现两个相同的数异或起来对答案是没有贡献的,
而区间一定是偶数,所以我们考虑先把所有在同一边的数都选完一定不劣,
然后再去同时选两边相同的数就可以了。代码。
A. MEX Game 1
Alice and Bob play yet another game on an array \(a\) of size \(n\). Alice starts with an empty array \(c\). Both players take turns playing, with Alice starting first.
On Alice's turn, she picks one element from \(a\), appends that element to \(c\), and then deletes it from \(a\).
On Bob's turn, he picks one element from \(a\), and then deletes it from \(a\).
The game ends when the array \(a\) is empty. Game's score is defined to be the MEX\(^\dagger\) of \(c\). Alice wants to maximize the score while Bob wants to minimize it. Find game's final score if both players play optimally.
\(^\dagger\) The \(\operatorname{MEX}\) (minimum excludant) of an array of integers is defined as the smallest non-negative integer which does not occur in the array. For example:
- The MEX of \([2,2,1]\) is \(0\), because \(0\) does not belong to the array.
- The MEX of \([3,1,0,1]\) is \(2\), because \(0\) and \(1\) belong to the array, but \(2\) does not.
- The MEX of \([0,3,1,2]\) is \(4\), because \(0\), \(1\), \(2\) and \(3\) belong to the array, but \(4\) does not.
\(1 \le n \le 2\times 10^5\)。
成功把自己绕进去了,做完 B 才绕出来——但是这道题确实很简单啊。
首先,这个东西跟序列的顺序丝毫没有关系,只会与每个数出现的个数相关。
分析一下每次如何选择的,
假设可能的答案 \(ans \ge i\),那么 \([0,i-1]\) 的 \(i\) 个数一定是会被选上的(这个思路也是 E 的)。
那么考虑当 Alice 知道她希望选哪些数的时候,那么一定是按照个数 从小到大 依次选,这样一定是最优的。
再放到这道题中,因为每次 Bob 也只能拿掉一个,所以当剩余 \(2\) 个 \(x\) 时选 \(x\) 并不一定是最优的,
所以我们每一次选的都是选只剩一个的最优。
想到这里就做完了,因为如果 \([0,i-1]\) 中有两个数都是 \(1\),那么你一定选不了,
也就是说答案就是第二个 \(1\) 出现的位置,注意 \(0\) 的情况可能要特殊处理一下。
这道题感觉就是直接模拟一下怎么去选择,不要一直想二分从而把自己绕进去,Alice 和 Bob 的策略都要考虑。
代码。
B. Non-Palindromic Substring
A string \(t\) is said to be \(k\)-good if there exists at least one substring\(^\dagger\) of length \(k\) which is not a palindrome\(^\ddagger\). Let \(f(t)\) denote the sum of all values of \(k\) such that the string \(t\) is \(k\)-good.
You are given a string \(s\) of length \(n\). You will have to answer \(q\) of the following queries:
- Given \(l\) and \(r\) (\(l \lt r\)), find the value of \(f(s_ls_{l + 1}\ldots s_r)\).
\(^\dagger\) A substring of a string \(z\) is a contiguous segment of characters from \(z\). For example, "\(\mathtt{defor}\)", "\(\mathtt{code}\)" and "\(\mathtt{o}\)" are all substrings of "\(\mathtt{codeforces}\)" while "\(\mathtt{codes}\)" and "\(\mathtt{aaa}\)" are not.
\(^\ddagger\) A palindrome is a string that reads the same backwards as forwards. For example, the strings "\(\texttt{z}\)", "\(\texttt{aa}\)" and "\(\texttt{tacocat}\)" are palindromes while "\(\texttt{codeforces}\)" and "\(\texttt{ab}\)" are not.
\(2 \le n \le 2\times 10^5\)。
CF 出哈希题还是**的。
看了题目,手玩样例,找找规律,容易发现:
\(k=1\) 一定是不好的。
- 对于
aaaaa
这种全部相同的字符串答案是 \(0\)。 - 对于
ababababab
这种交替的字符串,则所有奇数的 \(k\) 都是不好的,都不能算入,而偶数的 \(k\) 都是好的。 - 对于其他情况的 回文串,只会在 \(k=len\) 时不好,也就是 \(2 \sim len-1\) 都是答案。
- 对于其他情况(一定不是回文串),所有的 \(2 \sim len\) 都是好的。
于是就做完了,而如何判断一个字符串回文就需要用到哈希。
由于是 CF,为了防止 room 里面有人对着你的代码 hack,所以最好写随机模数的双哈希。
而笔者在场上写的确定模数的双哈希,好在并没有被 hack。代码。
C. Tree Compass
You are given a tree with \(n\) vertices numbered \(1, 2, \ldots, n\). Initially, all vertices are colored white.
You can perform the following two-step operation:
- Choose a vertex \(v\) (\(1 \leq v \leq n\)) and a distance \(d\) (\(0 \leq d \leq n-1\)).
- For all vertices \(u\) (\(1 \leq u \leq n\)) such that \(\text{dist}^\dagger(u,v)=d\), color \(u\) black.
Construct a sequence of operations to color all the nodes in the tree black using the minimum possible number of operations. It can be proven that it is always possible to do so using at most \(n\) operations.
\(^\dagger\) \(\text{dist}(x, y)\) denotes the number of edges on the (unique) simple path between vertices \(x\) and \(y\) on the tree.
\(1 \le n \le 2 \times 10^3\)。
和上一场的 CF 构造一样——数据范围是给 checker 用的!!!
首先对于树上一层一层染色,我们容易想到对于一条直径来做,
于是从简到难,我们先到考虑一条链的情况嘛。
画图自己分析手玩一下,发现:
- 对于奇数,我们一定对于中间的那个点不断染,次数为 \(\frac n 2 +1\)。
- 对于偶数,且 \(4 \nmid n\),我们时构造不出来 \(\frac n 2\) 的情况的,同样对中间的一个点进行操作,最后剩下的两个点一定需要两次操作,所以操作次数也是 $\frac n 2 +1 $。
- 对于 \(4 \nmid n\),我们同样想去构造 \(\frac n 2\),发现是可以的,比如 \(4\) 的情况,我们可以对于第二个点和第三个点分别做一次 \(d=1\) 的染色,这样的答案是 \(\frac n 2\)。
那推广到树上面呢?
发现前两种情况是完全可以的,因为都是对于中间点操作,
而对于第三种情况,我们也希望一种对于中间点操作的策略,
这就很容易想歪,但如果我们对于 \(n=4\) 的看待是中间两个点各操作 \(d=1\),那么推广到 \(n=8\) 我们就可以再操作一次 \(d=2\),
于是这也是对于中间的点操作,所以依然是满足条件的。
我们就做完了,每一次只需要找出直径,对直径的中点做相应的操作即可。
时间复杂度是线性的。。。代码。
D1. Counting Is Fun (Easy Version)
An array \(b\) of \(m\) non-negative integers is said to be good if all the elements of \(b\) can be made equal to \(0\) using the following operation some (possibly, zero) times:
- Select two distinct indices \(l\) and \(r\) (\(1 \leq l \color{red}{\lt} r \leq m\)) and subtract \(1\) from all \(b_i\) such that \(l \leq i \leq r\).
You are given two positive integers \(n\), \(k\) and a prime number \(p\).
Over all \((k+1)^n\) arrays of length \(n\) such that \(0 \leq a_i \leq k\) for all \(1 \leq i \leq n\), count the number of good arrays.
Since the number might be too large, you are only required to find it modulo \(p\).
\(3 \le n \le 400,1 \le k \le n\)。
主打一个经验和找规律。
感觉这个东西很常见啊,但是有什么性质呢?
发现对于一个合法的 \(a\) 数组,那么 \(\forall 1 \le i\le n,a_i \le a_{i-1}+a_{i+1}\) 是充要条件!!!
为什么呢?(建议自己画图理解一下)
简单来说,就是当 \(i\) 不满足条件时,两边的所有区间一定都包含了 \(i\),不然还可以拓展,而这种情况 \(a_i\) 就会大了。
那么有了这个性质,你似乎随便怎么 dp 一下就可以了,
dp 是简单的,你只需要记录一下最后的两个元素是什么,
每次转移的时候讨论一下如何让 \(i-1\) 合法即可。
再用前缀和处理一下就可以做到 \(\mathcal O(n^3)\)。代码。
D2. Counting Is Fun (Hard Version)
题意与 D1 相同。
\(3 \le n \le 3000,1 \le k \le n\)。
如何优化 D1 的 dp?
直接做根本不可能啊!!!
所以我们考虑反过来想,考虑有多少个不合法的位置满足 \(a_i \gt a_{i-1}+a_{i+1}\)?
而精确的个数很明显依然不好考虑,
于是很自然地想到 容斥,于是我们令 \(f_i\) 表示不合法位置个数 \(\ge i\) 的方案数,最后用容斥系数乘一下就可以了。
那么我们就只需要最后一个元素是什么啦~
我们设 \(f_{i,j,x}\) 表示当前枚举到第 \(i\) 个,最后一个元素为 \(x\),当前不合法位置个数至少为 \(j\) 的方案数。
看起来还是 \(\mathcal O(n^3)\) 的?——你先别急。
那么转移其实就是枚举一下当前这一位是否合法,
如果我们钦定当前第 \(i-1\) 位合法,那么上一位可以选任何东西(不合法也不用管,因为我们要容斥),也就是加上 \(\displaystyle \sum_{y=0}^K f_{i-1,j,y}\)。
而另一种情况就是上一位不合法,那么我们就需要通过 \(i-2\) 的位置来确定 \(i-1\) 的选择,
也就是 \(f_{i,j,x} + = \displaystyle \sum_{y=0}^{K-x} (K-x-y) \times f_{i-2,j-1,y}\)。(这时好理解的吧)
而现在考虑如何实现这两个东西,发现其实是可以预处理的!
第一种情况直接前缀和就行,而第二种情况可以按照 \(x\) 从大到小枚举,顺便可以再图中记录有关 \(y\) 的前缀和——因为每一次 \(K-x\) 只会变大 \(1\)。
实现可以具体看代码,于是这样除去 \(j\) 的枚举,我们就可以做到 \(\mathcal O(n^2)\)。
回到刚才设计 dp 时的问题—— \(j\) 怎么办捏?
发现 \(j\) 本质不同的情况其实就只是奇数和偶数两种——于是你不是做完了?
因为奇数最后的容斥系数是相同的,偶数的容斥系数是相同的,而转移中奇偶也不影响,
所以我们完全可以在 \(\mathcal O(n^2)\) 的时间内解决啦。
具体实现可以看代码。代码。
E1. MEX Game 2 (Easy Version)
Alice and Bob play yet another game on an array \(a\) of size \(n\). Alice starts with an empty array \(c\). Both players take turns playing, with Alice starting first.
On Alice's turn, she picks one element from \(a\), appends that element to \(c\), and then deletes it from \(a\).
On Bob's turn, he picks at most \(k\) elements from \(a\), and then deletes it from \(a\).
The game ends when the array \(a\) is empty. Alice's score is defined to be the MEX\(^\dagger\) of \(c\). Alice wants to maximize her score while Bob wants to minimize it. Find Alice's final score if both players play optimally.
The array will be given in compressed format. Instead of giving the elements present in the array, we will be giving their frequencies. Formally, you will be given \(m\), the maximum element in the array, and then \(m + 1\) integers \(f_0, f_1, \ldots, f_{m}\), where \(f_i\) represents the number of times \(i\) occurs in the array \(a\).
\(^\dagger\) The \(\operatorname{MEX}\) (minimum excludant) of an array of integers is defined as the smallest non-negative integer which does not occur in the array. For example:
- The MEX of \([2,2,1]\) is \(0\), because \(0\) does not belong to the array.
- The MEX of \([3,1,0,1]\) is \(2\), because \(0\) and \(1\) belong to the array, but \(2\) does not.
- The MEX of \([0,3,1,2]\) is \(4\), because \(0\), \(1\), \(2\) and \(3\) belong to the array, but \(4\) does not.
\(1 \le m \le 50\)。
好题啊。
首先发现这道题和 A 的区别(除去数据范围)就是每次可以删掉 \(k\) 个了,而最优策略的思路并没有太大变化。
按照 A 中的思路想,对于 Alice 而言,判断 \(ans \ge i\) 成立,也就是我是否能按照 \(f\) 从小到大的顺序选完 \([0,i-1]\) 中的所有数,
如果能选完,那么这就是成立的——不难发现答案是具有二分性的,所以可以二分答案(E1 直接暴力也能过)。
那么现在关键在于我们如何去 chk 一个答案是否合法?
此时顺序已经不重要了,所以我们一定可以按照 \(f\) 排序之后,让 Alice 从右往左依次选择,假设排序后数组为 \(a\)。
再想想——思考 Alice 的最优策略——每次选最小的,
那么为了保证我们排序后的序列保序,所以 Bob 的每一次的删除一定是保证 \(a_i\) 最小,后面的最优。
可是后面怎么才能最优呢?
既然 Alice 的顺序已经确定,所以我们只需要让 Bob 最优即可,
而 Bob 最优就是希望在选到某一个位置 \(i\) 的时候,\(a_i\) 已经没有了!!!
为什么没有了?因为在它选 \(i-1\) 时 \(a_i \le K\) 了!!!
我们把这个 \(i\) 称作中断点,也就是 Bob 赢的地方,
容易发现中断点不同,你前面的策略也是不同的,所以这似乎要枚举。
假设当前枚举到的中断点为 \(pos\),那么我们的目的就是让到 \(pos\) 的时候 \(a_{pos}\) 没有了。
而又要保证 Alice 的选择策略是从左往右(也就是序列保序),当选到 \(i\) 时我们的操作一定是在 保序的情况下 \(a_{pos}\) 尽可能小。
这是什么呢?
容易发现其实就是一个从后往前的推平操作,你直接暴力乱作一下就可以了。
细想一下,由于 \(n \le 50\),所以这道题似乎就做完了。
每次 chk 的时候我们用 \(\mathcal O(n)\) 的时间去枚举中断点,对于每一个中断点,我们从前往后模拟依次拿走 \(K\) 个,也就是模拟推平,
而这时可以用 \(\mathcal O(n)\) 的时间复杂度直接暴力做的,所以每一次 chk 的时间复杂度是 \(\mathcal O(n^3)\) 的。
你加上一个二分可以轻松通过,时间复杂度 \(\mathcal O(n^3 \log n)\)——但是直接暴力做也是丝毫没有问题的,还跑得飞快!!!
代码。
bool chk(int x){//答案是否能为 x
for(int i=0;i<x;i++) b[i]=a[i];
sort(b,b+x);
for(int i=0;i<x;i++) c[i]=b[i];
if(x==1) return true;
if(x==2) return b[1]>K;
if(b[1]<=K) return false;
for(int len=2;len<=x;len++){//枚举中断点
for(int i=0;i<len;i++) b[i]=c[i];
S[len]=0;//预处理后缀和
for(int i=len-1;i>=0;i--) S[i]=S[i+1]+b[i];
for(int i=1,id;i<len-1;i++){
ll s=0,nw=0;
for(int j=i;j<len;j++){//把 j 以后的都推平,也就是都 =b[j]
s=S[j]-1ll*(len-j)*b[j];
if(s<=K){id=j;break;}
}
nw=(K-s)/(len-id);
b[id]-=nw;
for(int j=id+1;j<len;j++) b[j]=b[id];
nw=(K-s)%(len-id);
for(int j=id;j<len&&nw;j++) --nw,--b[j];
}
if(b[len-1]<=K) return false;
}
return true;
}
E2. MEX Game 2 (Hard Version)
题意与 E1 相同。
\(1 \le m \le 10^5\)。
感觉和 E1 的思路其实差别并不大,那么现在我们来想想如何优化 E1 中的 chk 过程呢?
容易发现枚举中断点基本上是跑不掉的,那么如何快速完成判断一个中断点和一个序列是否合法呢?
感觉需要一些模拟:
在 E1 中我们也发现了,其实也就是一个不断推平的过程,
而推平过程中可能会出现很多个不同的数,但是我们发现当不同数的个数减到 \(2\) 时,其实就变成了一种很平凡的情况了。
具体来说,对于一个长度为 \(n\) 的序列 \(a_i,a_2,\cdots ,a_n\),它们的和为 \(s\),\(\forall 1 \le i \le n,a_i=\lfloor \frac {s+i-1}{n}\rfloor\),
也就是形如 \(x,x,\cdots ,x,x+1,\cdots,x+1\),我们把这样的东西记作 \((n,s)\)(因为只需要知道 \(n,s\) 其实就能唯一确定这个序列了),把这样的序列称之为 平的 序列。
发现这样的序列之后的操作也就有了一个固定的模式,我们对其进行一轮变换(Alice 删一个最小的,Bob 再拿走 \(K\) 个),
它会得到 \((n-1,s-\lfloor \frac s n \rfloor -K)\) ,而这个序列也一定是平的。
所以不断这样递归到 \(n=1\) 就可以知道答案了——
而反过来,我们可以从 \(n=0\) 往上递推出长度为 \(n\) 的平序列所对应的最大的 \(s\)。
这时一个简单 dp 可以完成的:
for(int i=1;i<=n+1;i++) f[i]=f[i-1]+K+(f[i-1]+K)/i;
而进而考虑,只要对于每一个中断点,我们找到第一个位置,使得它操作完之后,后面就变成一个平序列的地方,
这个位置后面的所有位置就是非常好处理的了。
发现这时非常好处理的,用双指针或者简单二分一下就可以完成,
因为对于一个位置的判断是好判断的,假设当前二分到的位置是 \(mid\),
也就相当于判断 \(mid\) 之前所有拿的 \(K\) 之和是否能后面是平的(后面没有平就一定是保序的)。
感觉比较抽象可以结合代码理解:
for(int i=2;i<=x;i++){//枚举中断点,s 是前缀和
int l=0,r=i;
while(l<r){
int mid=(l+r)/2;
if(s[i]-s[mid]-1ll*b[mid+1]*(i-mid)<=1ll*K*mid) r=mid;
else l=mid+1;
}
if(s[i]-s[l]<=1ll*l*K+f[i-l-1]) return false;//判断后面平的序列是否合法
}
这里是用二分实现的,但容易发现可以直接用双指针维护,可以少掉一个 \(\log\)。(不过也无所谓,反正能过)
于是这样我们就做完了,把每一次的 chk 优化到了 \(\mathcal O(n)\) 或者 \(\mathcal O(n \log n)\),可以顺利通过。代码。
Conclusion
- 类似于博弈论的题目一定去模拟情景,考虑双方的最优策略分别是什么即可,不要想复杂了。(A)
- 两人都是最优策略时考虑保证一人最优策略的顺序,再对另一个人的策略贪心。(E1)
- 优化代码时考虑哪些情况之后都规约到一种情况了,于是这部分的枚举时间可以大大减少。(E2)
- 构造题分析清楚再下手,有些非常高妙的构造可以考虑跑暴力。(C)
- dp 优化可以考虑 反过来做容斥!!!(D2)