[题解] CF2109 Codeforces Round 1025 (Div. 2) (A~E)
A
对于1:不能全是1,因为 n - 1 场战斗最多有 n - 1 个玩家赢。
对于0:相邻两项不能都是0,因为战斗总是有人赢,有人输。
Code
\(O(n)\)。
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (count(a.begin() + 1, a.end(), 1) == n) {
cout << "YES\n";
return;
}
for (int i = 2; i <= n; i++) {
if (a[i] == 0 && a[i - 1] == 0) {
cout << "YES\n";
return;
}
}
cout << "NO\n";
}
B
一个人负责调整棋子位置,一个人切棋盘。
切棋盘每次只能切掉一边,所以 横向的切 跟 纵向的切 是独立的。
调整棋子位置的人,把棋子放到中间最优,因为这样可以最小化 各种方向切的损失。(偶数个格子就没办法了,总有一边损失大 1 格)。
切棋盘的人,每次切掉的 行数(列数)越多越好。
设 n 为行(列)的数量。n 为偶数时,能切掉 n / 2,还剩下 n / 2;n 为奇数时,能切掉 n / 2,还剩下(n + 1) / 2。模拟这个切的过程。
对于怪物的初始位置,只能影响第一次切。因为结果可以在 \(\log\) 时间内计算出,我们各个方向都切一遍取最大值也是可以接受的。
详见代码。
Code
\(O(T*(\log n + \log m))\)。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
void solve() {
i64 n, m, a, b;
cin >> n >> m >> a >> b;
auto calc = [&](i64 x, i64 y) {
i64 res = 0;
while (x > 1) {
x = (x + 1) / 2;
res++;
}
while (y > 1) {
y = (y + 1) / 2;
res++;
}
return res;
};
i64 ans = min({calc(n - a + 1, m), calc(a, m), calc(n, m - b + 1), calc(n, b)});
cout << ans + 1 << '\n';
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int tt = 1;
cin >> tt;
while (tt--) {
solve();
}
return 0;
}
C1
我们只知道n,不知道x,只能把x做成一个确定的数,再通过这个数做出n。即,"add (n - 确定的数)"。
基于此,我们可以观察到 digit操作可以很快的减小 x。因为x最大是1e9,第一次digit可以把x变成两位数,第二次digit可以把x变成 1~16之间的数。证明不能是17,可以考虑构造出17的两位数是89或者98。对于x <= 1e9,显然digit(x) <= 81。18显然更不可能。
然后我们再用5次操作把x变成1就可以,-8, -4, -2, -1,最后add (n - 1)。
稳定7次操作,可以通过。注意输入输出要求。
Code
void solve() {
int n;
cin >> n;
int res;
cout << "digit" << endl; // -> xx
cin >> res;
cout << "digit" << endl; // -> 1x
cin >> res;
cout << "add -8" << endl;
cin >> res;
cout << "add -4" << endl;
cin >> res;
cout << "add -2" << endl;
cin >> res;
cout << "add -1" << endl;
cin >> res;
cout << "add " << n - 1 << endl;
cin >> res;
cout << "!" << endl;
cin >> res;
}
C2
次数要求严苛了,我们考虑其他的办法。
有个性质就是说,能被9整除的数,各位数字相加一定也能被9整除。这个性质很常见(3也有这个性质),我们可以基于此入手。
因为一次digit就能控制到两位数了,满足被9整除的两位数:18, 27, 36, 45, 54, 63, 72, 81, 90。一位数有个9。我们发现再次digit稳定变9。
所以我们可以mul 9,然后 \(2\) 次 digit把x做成9,然后add (n - 9)。
Code
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
void solve() {
int n;
cin >> n;
int res;
cout << "mul 9" << endl;
cin >> res;
cout << "digit" << endl; // -> xx
cin >> res;
cout << "digit" << endl; // -> 1x
cin >> res;
cout << "add " << n - 9 << endl;
cin >> res;
cout << "!" << endl;
cin >> res;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int tt = 1;
cin >> tt;
while (tt--) {
solve();
}
return 0;
}
C3
神人题。可以从题目中的1e9入手,或者从 C2 中的 9 的个数来入手。
手玩一下1 * 999999999,2 * 999999999.....k * 999999999。可以发现每次都是把数从最低位往最高位挪。
但是每次digit都是相同的,81。
直接利用这个性质可以在 \(3\) 次操作把x做成n,特判n == 81的情况只用 \(2\) 次。
Code
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
void solve() {
int n;
cin >> n;
int res;
cout << "mul 999999999" << endl;
cin >> res;
cout << "digit" << endl;
cin >> res;
if (n == 81) {
cout << "!" << endl;
cin >> res;
} else {
cout << "add " << n - 81 << endl;
cin >> res;
cout << "!" << endl;
cin >> res;
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int tt = 1;
cin >> tt;
while (tt--) {
solve();
}
return 0;
}
D
仔细读题,深入理解。
发现可以走回头路,所以我们可以在两个点间原地踏步,从而达到 奇偶性相同 的 \(A_i\) ,大的可以当小的用。
我们发现答案只与 \(\sum A_i\) ,和 \(A_i\) 的奇偶性有关。
\(\sum A_i\) 是奇数,我们要找出偶数最长可达长度,\(\sum A_i\) 是偶数,我们要找出奇数最长可达长度。
我们发现我们只需要知道 \(\sum A_i\) 和 \(\sum A_i - \min\{A_i| A_i为奇数\}\)。
关键最短奇数长度路,和最短偶数长度路,我们可以建个双层图,BFS来得到。详情见代码。
Code
\(O(n+m+ℓ)\)。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
const int maxn = 4e5 + 5;
vector<int> g[maxn];
void solve() {
int n, m, l;
cin >> n >> m >> l;
for (int i = 1; i <= 2 * n; i++) {
g[i].clear();
}
i64 mi = (i64)1e18;
vector<i64> a(l + 1);
for (int i = 1; i <= l; i++) {
cin >> a[i];
if (a[i] % 2) {
mi = min(mi, a[i]);
}
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v + n);
g[v + n].push_back(u);
g[v].push_back(u + n);
g[u + n].push_back(v);
}
vector<i64> d(2 * n + 1, (i64)1e18);
vector<int> vis(2 * n + 1);
d[1] = 0;
queue<int> q;
q.push(1);
while (q.size()) {
int x = q.front();
q.pop();
if (vis[x]) {
continue;
}
vis[x] = 1;
for (auto &v : g[x]) {
if (d[v] > d[x] + 1) {
d[v] = d[x] + 1;
q.push(v);
}
}
}
vector<int> ans(n + 1);
i64 sum = accumulate(a.begin() + 1, a.end(), 0LL);
if (mi == (i64)1e18) {
if (sum % 2) {
for (int i = 1; i <= n; i++) {
ans[i] |= d[i + n] <= sum;
}
} else {
for (int i = 1; i <= n; i++) {
ans[i] |= d[i] <= sum;
}
}
} else {
if (sum % 2) {
for (int i = 1; i <= n; i++) {
ans[i] |= d[i + n] <= sum;
ans[i] |= d[i] <= sum - mi;
}
} else {
for (int i = 1; i <= n; i++) {
ans[i] |= d[i] <= sum;
ans[i] |= d[i + n] <= sum - mi;
}
}
}
for (int i = 1; i <= n; i++) {
cout << ans[i];
}
cout << '\n';
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int tt = 1;
cin >> tt;
while (tt--) {
solve();
}
return 0;
}
E
发现每个点i能不能被拿,只与i ~ n里拿了几次有关。
然后我们就可以倒着 dp。想出无后效性的 dp 数组形式。
Hint 2: 让
dp[i][j]表示在后缀si,si+1,…,sn上恰好下出j步的方法数。
显然 i 位的状态从 i + 1的状态转移。每一位可以被重复拿多次\((<=k)\),我们还要枚举这个多次。如若i位是0,那么他只能作为 i ~ n中 第 奇数次 的拿 插入 总拿数,这里还要用到组合数。
我们枚举 dp 数组的两维i、j,再枚举第 i 位总共要拿l次, l <= j。
具体细节写在代码注释了。
Code
\(O(nk^2)\)。
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
const int mod = 998244353;
const int maxn = 5e2 + 2;
i64 f[maxn], g[maxn];
i64 ksm(i64 a, i64 n) {
i64 ans = 1;
a %= mod;
while (n) {
if (n & 1) {
ans = ans * a % mod;
}
a = a * a % mod;
n >>= 1;
}
return ans;
}
void init() {
f[0] = 1;
g[0] = 1;
for (int i = 1; i < maxn; i++) {
f[i] = f[i - 1] * i % mod;
g[i] = g[i - 1] * ksm(i, mod - 2) % mod;
}
}
inline i64 C(i64 n, i64 m) {
if (m > n || n < 0 || m < 0) {
return 0;
}
if (m == 0) {
return 1;
}
return f[n] * g[m] % mod * g[n - m] % mod;
}
void solve() {
int n, k;
cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
char c;
cin >> c;
a[i] = c - '0';
}
// Hint 2: 让 dp[i][j]表示在后缀 si,si+1,…,sn上恰好下出 j步的方法数。
vector<vector<i64>> dp(n + 2, vector<i64>(k + 1));
dp[n + 1][0] = 1;
// 倒着看拿每 i 位
for (int i = n; i >= 1; i--) {
// i ~ n 位可以有 j 次拿
for (int j = 0; j <= k; j++) {
// 这一位可以有 l 次拿
// 假设 | 为当前位拿,& 为 i + 1 ~ n 位拿的
// 具象化 j == 9 的情况(奇数):
// 当 a[i] == 0 的时候,最大的 l 情况是 |&|&|&|&|
// 当 a[i] == 1 的时候,最大的 l 情况是 &|&|&|&|&
// j == 8 的情况(偶数):
// 当 a[i] == 0 的时候,最大的 l 情况是 |&|&|&|&
// 当 a[i] == 1 的时候,最大的 l 情况是 &|&|&|&|
// 用这个计算 l 的上限
int u = j / 2 + (a[i] == 0 && j % 2);
for (int l = 0; l <= u; l++) {
// 求组合数的时候,| 只能插在 奇数位 或者 偶数位 (取决于 a[i]
// 其实就是 l 的上限,如果不插 | 就自动插 &
dp[i][j] = (dp[i][j] + dp[i + 1][j - l] * C(u, l)) % mod;
}
}
}
// 从 1 ~ n 里拿了 k 次
cout << dp[1][k] << '\n';
}
int main() {
init();
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int tt = 1;
cin >> tt;
while (tt--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号