Codeforces Round 868 (Div. 2) ABCDE
F 待补 TAT
A. A-characteristic
题意
给定 \(n,k\),要求构造一个只由 \(1\) 和 \(-1\) 组成的长度为 \(n\) 的数列 \(a\),使得 \(\sum_{1\le i<j\le n}[a_i\cdot a_j=1]=k\)。无解输出 \(\texttt{NO}\),有解输出 \(\texttt{YES}\) 和任意一个解。
思路
设 \(1\) 的个数为 \(a\),\(-1\) 的个数 \(b=n-a\),则我们需要使 \(\dbinom a2+\dbinom b2=\dfrac{a(a-1)}2+\dfrac{b(b-1)}2=k\)。
枚举即可,时间复杂度 \(O(\sum n)\)。
代码
点击查看代码
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
using namespace std;
int tt, n, k;
void solve() {
cin >> n >> k;
f(a, 1, n) {
int b = n - a;
if (a * (a - 1) / 2 + b * (b - 1) / 2 == k) {
cout << "YES\n";
f(i, 1, a) cout << "1 ";
f(i, 1, b) cout << "-1 ";
cout << '\n';
return;
}
}
cout << "NO\n";
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> tt;
while (tt--) solve();
return 0;
}
B. Sort with Step
题意
给定一个 \(1\) 到 \(n\) 的排列 \(p\) 和一个 \(k\)。你可以若干次交换 \(p_i\) 和 \(p_j\),使得 \(p\) 变为升序,其中 \(|i-j|=k\)。
你还可以在一开始交换任意两个 \(p_i\) 和 \(p_j\) 一次。
你的任务是判断:
- 能否在一开始不用任意交换的情况下,使得 \(p\) 变为升序;
- 如果不能,能否在一开始用一次任意交换的情况下,使得 \(p\) 变为升序。
如果满足条件 1 输出 \(\texttt0\);如果不满足 1 但满足 2 输出 \(\texttt1\);如果条件 1, 2 都不满足输出 \(\texttt{-1}\)。
\(\sum n\le2\times10^5\)。
思路
我们发现,如果一开始不用任意交换,设一个数 \(x\) 的位置是 \(i\)(即 \(p_i=x\)),那么它可能移动到的位置只可能是 \(i+yk\),其中 \(y\in\mathbb Z\)。所以,只要存在 \(y\) 使得 \(i+yk=x\) ,那么它就可以移动到自己应该在的位置。这样就可以判断条件 1 是否满足。
任意交换的作用就是使两个本来不能移动到自己位置的数变得能够移动到自己位置。这样的操作具有 对称性。于是,只要 恰好有两个数 不满足存在 \(y\in\mathbb Z\) 使得 \(i+yk=x\),那么就可以通过一次任意交换使其满足。于是可以判断条件 2。
代码
点击查看代码
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
using namespace std;
int tt, n, k;
void solve() {
cin >> n >> k;
int x, cnt = 0;
f(i, 1, n) {
cin >> x;
if ((x - i) % k) ++cnt;
}
if (!cnt) cout << "0\n";
else if (cnt == 2) cout << "1\n";
else cout << "-1\n";
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> tt;
while (tt--) solve();
return 0;
}
C. Strongly Composite
题意
定义 强合数 为:合因子个数大于等于素因子个数的数(大于 \(1\))。如 \(4,8,9,12,16\) 等是强合数,而 \(2,3,5,6,7,10,15\) 等不是。
现在给定序列 \(a_1,a_2,\dots,a_n\),要求构造一个序列 \(b_1,b_2,\dots,b_k\)(\(k\) 未给定)满足:
- \(a_1a_2\cdots a_n=b_1b_2\cdots b_k\);
- \(b\) 中的所有数都是强合数;
- \(k\) 尽可能地大。
输出最大化的 \(k\)。如果 \(b\) 不存在,输出 \(0\)。
\(1\le t\le1000\),\(1\le\sum n\le1000\),\(2\le a_i\le10^7\)。
思路
显然,我们需要将 \(\prod a_i\) 进行质因数分解,然后考虑如何用这些质因数搭配出一个序列 \(b\)。
注意到题目中说 \(4\) 是强合数,也就是说所有 完全平方数 都是强合数。而质数显然不是强合数。
可以发现,如果用不同的质因子,至少需要 \(3\) 个才能构成一个强合数,而如果用相同的质因子只需要 \(2\) 个。
于是我们贪心地使用相同的质因子尽量凑完全平方数,如果某种质因子最后只剩下 \(1\) 个,那么我们凑齐 \(3\) 种还能搞出一个强合数。
时间复杂度 \(O\left(\sum\log a_i\right)\),如果使用 std::map 需要再带一个 \(\log\)。
代码
\(\Large\color{red}\textbf{血的教训:不要用 }\texttt{memset}\textbf{,否则 TLE 直接爆炸!!!!}\)
点击查看代码
#include <iostream>
#include <map>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
using namespace std;
typedef pair<int, int> pii;
int constexpr N = 1e7 + 10;
int tt, n;
map<int, int> mp;
void get_prime_factor(int x) {
for (int i = 2; i <= x / i; ++i)
while (!(x % i)) ++mp[i], x /= i;
if (x ^ 1) ++mp[x];
return;
}
void solve() {
cin >> n;
int x;
mp.clear();
f(i, 1, n) {
cin >> x;
get_prime_factor(x);
}
int ans = 0, tmp = 0;
for (pii i: mp) {
ans += (i.second >> 1);
tmp += (i.second & 1);
}
ans += tmp / 3;
cout << ans << '\n';
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> tt;
while (tt--) solve();
return 0;
}
*D. Unique Palindromes(字符串构造)
题意
令 \(p(t)\) 表示字符串 \(t\) 的不同回文子串数(不同即多次出现只算一次),\(p(s,m)\) 表示字符串 \(s\) 的 \(m\) 前缀的不同回文子串数,即 \(p(s,m)=p(t)\),其中 \(t=s[1..m]\)。如 \(t=\texttt{abcbbcabcb}\),则 \(p(t)=6\)(\(\texttt{a},\texttt{b},\texttt{c},\texttt{bb},\texttt{bcb},\texttt{cbbc}\)),\(p(t,5)=p(\texttt{abcbb})=5\)(\(\texttt{a},\texttt{b},\texttt{c},\texttt{bb},\texttt{bcb}\))。
给定整数 \(n\) 和 \(k\) 个条件,第 \(i\) 个条件用 \((x_i,c_i)\) 表示,意思是对于字符串 \(s\),满足 \(p(s,x_i)=c_i\)。
构造一个由小写拉丁字母组成的长度为 \(n\) 的字符串 \(s\),使其满足所有的 \(k\) 个条件。
\(3\le n\le2\times10^5\),\(1\le k\le20\),\(\sum n\le2\times10^5\),\(3\le x_1<x_2<\dots<x_k=n\),\(3\le c_1\le c_2\le\dots\le c_k\le\min\left(10^9,\frac{n(n+1)}2\right)\)。
思路
引理:如果在原字符串 \(s[1..n-1]\) 后面添加任意一个字符 \(s[n]\),回文子串个数增加不超过 \(1\)。
证明:考虑反证法,如果增加超过 \(1\),说明最后一个字符参与构成了至少 \(2\) 个不同的新的回文子串 \(s[i..n]\) 和 \(s[j..n]\)。不妨设 \(i<j\),那么由于 \(s[i..n]\) 的对称性,\(s[i..i+(n-j+1)-1]\) 也是回文子串。而这个回文子串也是 \(s[i..n-1]\) 的回文子串,必然在之前就考虑过了。因此构成矛盾,假设不成立。
因此,无解的充分条件即为 \(x_i-x_{i-1}<c_i-c_{i-1}\)(设 \(x_0=c_0=0\))。下面说明如果不满足这个条件则一定可以构造出合法字符串。
首先考虑单独一个条件 \((x,c)\) 如何满足。我们可以构造如下的字符串:
这是由于 \(\texttt{d},\texttt{dd},\texttt{ddd},\dots\) 以及 \(\texttt{a},\texttt{b},\texttt{c}\) 都是回文子串。当然,\(\texttt{d}\) 也可以换成不为 \(\texttt{a},\texttt{b},\texttt{c}\) 的任意一个字符。
接下来考虑如何满足所有 \(k\) 个条件。第 \(1\) 个条件如上。对于剩下的条件,我们可以像上面一样构造,只不过连续重复的字符段长度应该为 \(c\),并且这个重复的字符不能在前面出现过。由于 \(k\le20\),\(26\) 个小写拉丁字母一定是够用的。
代码
点击查看代码
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
using namespace std;
int constexpr N = 2e5 + 10;
char constexpr abc[3] = {'a', 'b', 'c'};
int tt, n, k, x[21], c[21];
char s[N];
void solve() {
cin >> n >> k;
f(i, 1, k) cin >> x[i];
f(i, 1, k) cin >> c[i];
char ch = 'd';
int t = 0;
f(i, 1, k) {
if (x[i] - x[i - 1] < c[i] - c[i - 1]) return cout << "NO\n", void();
int l = x[i - 1] + 1, r = x[i - 1] + c[i] - c[i - 1];
if (i == 1) r -= 3;
f(j, l, r) s[j] = ch;
++ch;
f(j, r + 1, x[i]) s[j] = abc[t], (++t) %= 3;
}
cout << "YES\n";
f(i, 1, n) cout << s[i];
cout << '\n';
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> tt;
while (tt--) solve();
return 0;
}
*E. Removing Graph(SG 函数)
题意
Alice 和 Bob 正在一张简单无向图上玩一个游戏。图中有 \(n\) 个节点,且每一个节点的度数都为 \(2\)。图中可能有若干个连通块。
Alice 和 Bob 轮流行动,Alice 先手。每次行动中,玩家可以选择 \(k\)(\(k\in[l,r]\))个节点删去,且这些节点构成原无向图的一个 连通子图。无法行动的玩家判负。
给定 \(n,l,r\) 以及 \(n\) 条边 \((u_i,v_i)\),问赢得游戏的人是 Alice 还是 Bob。输出赢家的名字。
\(3\le n\le2\times10^5\),\(1\le l<r\le n\)。
思路
学习笔记:Nim 游戏基础 - f2021ljh - 博客园
每个节点的度数为 \(2\),也就是说这个无向图是由许多环构成的。
显然,每一个环是独立的,那么根据 SG 定理,整个游戏的 \(\operatorname{SG}\) 值即为 每个环的 \(\operatorname{SG}\) 值的异或和。而环的 \(\operatorname{SG}\) 值只与它的大小有关。
对于一个环,第一次行动时显然会破环成链。于是可以先求链的 \(\operatorname{SG}\) 值。
设大小为 \(x\) 的环的 \(\operatorname{SG}\) 值为 \(cycle(x)\),大小为 \(x\) 的链的 \(\operatorname{SG}\) 值为 \(chain(x)\)。
把大小为 \(x\) 的环中的一段拿走,可以形成的链有 \(r-l+1\) 种情况,即
对于一条长度为 \(x\) 的链,拿走中间的一段,会形成两条链,也就是变成了两个子问题,即
注意 两种情况的 \(\operatorname{SG}\) 值之间应该做 \(\operatorname{mex}\) 运算,而两个子状态的 \(\operatorname{SG}\) 值之间应该做异或运算。
然而这样只是 \(O(n^3)\) 的,我们还需要发现一些关键性质来优化。
令 \(l=4\),\(r=14\),手算得
| \(i\) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| \(chain(i)\) | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 4 | 4 |
| \(cycle(i)\) | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 0 | 0 |
可以发现:
- \(chain(i)=\left\lfloor\dfrac il\right\rfloor\)(\(0\le i\le l+r-1\))(证明见下);
- \(chain(i)>0\)(\(i\ge l\)),因为总可以令 \(a=b\)(由于 \(l<r\)),而两个相同的数的异或值为 \(0\);
- \(cycle(i)=\begin{cases}chain(i),&i\le l+r-1,\\0,&i>l+r-1.\end{cases}\)
在 1, 2 成立的情况下,3 成立是显而易见的(注意不要把 \(cycle(i)\) 误以为是 \(cycle(i-r),\dots,cycle(i-l)\) 的 \(\operatorname{mex}\) 值)。
下面证明 \(chain(i)=\left\lfloor\dfrac il\right\rfloor\) 在 \([0,l+r-1]\) 上成立:
显然 \(\forall\,0\le i\le l-1\) 有 \(chain(i)=0=\left\lfloor\dfrac il\right\rfloor\)。下面考虑 \(l\le i\le l+r-1\) 的情况。
假设命题在 \([0,i-l]\) 上都成立。
\(chain(i)\ge\left\lfloor\dfrac il\right\rfloor\):令 \(a=0\),\(b\) 为 \([\max\{i-r,0\},i-l]\) 上的任意一个数,则有\[chain(i)\ge\operatorname{mex}\{chain(b)\mid\max\{i-r,0\}\le b\le i-l\}=\left\lfloor\dfrac il\right\rfloor. \]\(chain(i)\le\left\lfloor\dfrac il\right\rfloor\):对于所有 \(a,b\) 满足 \(a,b\ge0\wedge i-r\le a+b\le i-l\),有
\[chain(a)\oplus chain(b)\le chain(a)+chain(b)=\left\lfloor\frac al\right\rfloor+\left\lfloor\frac bl\right\rfloor\le\left\lfloor\frac{a+b}l\right\rfloor\le\left\lfloor\frac{i-l}l\right\rfloor=\left\lfloor\frac il\right\rfloor-1. \]于是 \(chain(i)=\left\lfloor\dfrac il\right\rfloor\)。根据数学归纳法,命题成立。
代码
点击查看代码
#include <vector>
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
using namespace std;
int constexpr N = 2e5 + 10;
int n, l, r, ans;
vector<int> edge[N];
int col[N], tot, siz[N];
void dfs(int u) {
col[u] = tot;
++siz[tot];
for (int v: edge[u])
if (!col[v]) dfs(v);
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> l >> r;
int u, v;
f(i, 1, n) {
cin >> u >> v;
edge[u].push_back(v), edge[v].push_back(u);
}
f(i, 1, n) if (!col[i]) ++tot, dfs(i);
f(i, 1, tot) if (siz[i] <= l + r - 1) ans ^= (siz[i] / l);
if (ans) cout << "Alice\n";
else cout << "Bob\n";
return 0;
}
F. Random Walk
题意
一棵 \(n\) 个节点的树,每个节点 \(v\) 有一个计数器 \(c(v)\)。一开始你在点 \(s\),且 \(c(s)=1\),而 \(\forall\,v\ne s,c(v)=0\)。
你的目标是移动到点 \(t\)。假设你在点 \(v\),你可以按照下列方式移动:
- 均匀随机地选择一个 \(v\) 的相邻节点 \(to\)(\(to\) 是 \(v\) 的相邻节点当且仅当树中存在一条边 \((v,to)\));
- 移动到 \(to\),并且令 \(c(to)\) 累加 \(1\)。
在到达 \(t\) 之前,你将会不停地按照上述方式移动。
对于每个节点 \(v\),求 \(c(v)\) 的期望 \(\bmod998244353\)。\(2\le n\le2\times10^5\)。

浙公网安备 33010602011771号