アシマ / Dream Blue
1344 字
7 分钟
训练赛3
题目链接
A. Submission Bait
题解
先考虑只有一种权值的情况。显然此时若 为奇数,则先手必胜,否则先手必败。
然后考虑多种权值。发现若所有权值的出现次数均为偶数,则先手必败,否则先手仅需选择最大的出现次数为奇数的权值,即可令后手进入必败状态。
于是仅需检查是否有一种权值出现次数为奇数即可。
代码
void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; map<int, int> cnt; for (int i = 1; i <= n; i++) { int x; cin >> x; cnt[x]++; } if (any_of(cnt.begin(), cnt.end(), [](const pair<int, int> &x) { return x.second % 2 == 1; })) { cout << "YES\n"; } else { cout << "NO\n"; }}
B. Array Craft
题解
令 均为 ,从 开始,分别向左右构造成 交替的形式。
前缀和满足如下形式:
后缀和满足如下形式:
均满足要求
代码
void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; int x, y; cin >> x >> y; vector<int> ans(n + 1); for (int i = y; i <= x; i++) { ans[i] = 1; } for (int i = x + 1; i <= n; i++) { ans[i] = -ans[i - 1]; } for (int i = y - 1; i >= 1; i--) { ans[i] = -ans[i + 1]; } for (int i = 1; i <= n; i++) { cout << ans[i] << " \n"[i == n]; }}
C. Mad MAD Sum
题解
打表发现在第二次操作后,数组一定单调递增,且除了最大的权值外每种权值至少出现 次,每次操作后,数组右移一位。
代码
void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; ll sum = 0; vector<int> a(n + 1); for (int i = 1; i <= n; i++) { cin >> a[i]; } for (int j = 1; j <= 2; j++) { int tmp = 0; map<int, int> mp; for (int i = 1; i <= n; i++) { sum += a[i]; mp[a[i]]++; if (mp[a[i]] >= 2) { tmp = max(tmp, a[i]); } a[i] = tmp; } } ll tmp = 0; for (int i = 1; i <= n; i++) { tmp += a[i]; } for (int i = n; i >= 1; i--) { sum += tmp; tmp -= a[i]; } cout << sum << '\n';}
D. Linear Maze
题解
当访问到普通房间时,步数 ,如果访问到特殊房间,则步数 后翻倍,显然访问到特殊房间后,回到该房间需要重新走相同的路程
代码
void idol_produce(int testCase) { /*Code Here*/ int n, k; cin >> n >> k; vector<int> a(n + 1); for(int i = 1;i <= k; i ++){ int x; cin >> x; a[x] = 1; } ll ans = 0; for(int i = 1;i <= n; i++){ ans ++; if(a[i]) ans *= 2; } cout << ans << '\n';}
E. Grid Puzzle
题解
显然,由于 只能对相邻两行进行,对于 行,至多只进行 次 操作,否则使用 次覆盖行操作一定更优。
故考虑 ,令 表示覆盖了前 行,且第 行用 覆盖了第 行的 个格子的最小操作数
代码
void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; vector<int> a(n + 1); for (int i = 1; i <= n; i++) cin >> a[i]; vector<vector<int> > dp(n + 1, vector<int>(3, INF)); dp[0][0] = 0; for (int i = 1; i <= n; i++) { if (a[i] == 0) dp[i][0] = min({dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]}); else dp[i][0] = min({dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]}) + 1; if (a[i] <= 2) { dp[i][0] = min(dp[i][0], dp[i - 1][1]); dp[i][1] = min({dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]}) + 1; } else if (a[i] <= 4) { dp[i][1] = dp[i - 1][2] + 1; dp[i][2] = dp[i - 1][1] + 1; } } cout << min({dp[n][0], dp[n][1], dp[n][2]}) << '\n';}
F. Catch the Mole
题解
对于树高根号分治,目标是把老鼠逼到一条链上,最后二分找到位置
先对一个叶子节点进行 次无效询问,这样所有树高小于 的点必然不存在老鼠
对于树高大于 的,从根节点开始递归询问。对于一个节点的儿子,只有树高大于 才被询问,小于 的直接跳过即可
询问为真或者只有一个儿子的时候就直接递归进去,为假就下一个儿子因此这样的询问最多 次。
最后二分即可完成,注意每次查询失败的时候,左右边界都要向根偏移一个位置
代码
const int N = 70;
int ask(int x) { cout << "? " << x << endl; int res; cin >> res; return res;}
# define answer(x) do { cout << "! " << x << endl; return;} while (0)
void idol_produce(int testCase) { /*Code Here*/ int n; cin >> n; vector<vector<int>> e(n + 1); for (int i = 1; i <= n - 1; i++) { int u, v; cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } vector<int> father(n + 1, 0), h(n + 1); function<void(int, int)> dfs = [&](int u, int fa) -> void { for (auto v: e[u]) { if (v == fa) { continue; } father[v] = u; dfs(v, u); h[u] = max(h[u], h[v] + 1); } }; dfs(1, 0); int tmp = find(h.begin() + 1, h.end(), 0) - h.begin(); for (int i = 1; i <= N; i++) { if (ask(tmp) == 1) { answer(tmp); } } function<int(int)> dfs2 = [&](int u) -> int { vector<int> son; for (auto v: e[u]) { if (v == father[u] || h[v] < N) { continue; } son.emplace_back(v); } if (son.empty()) { return u; } for (auto x: son) { if (x == son.back() || ask(x) == 1) { return dfs2(x); } } assert(false); }; int end = dfs2(1); vector<int> a; for (int i = end; i != 0; i = father[i]) { a.emplace_back(i); } reverse(a.begin(), a.end()); int l = 0, r = a.size() - 1; while (l < r) { int mid = (l + r + 1) >> 1; if (ask(a[mid]) == 1) { l = mid; } else { r = mid - 1; l = max(0, l - 1); r = max(0, r - 1); } } answer(a[l]);}