Codeforces Round 992 (Div. 2) 题解
Codeforces Round 992 (Div. 2) 题解
给定长为n的数组和整数k,AB两人玩一个游戏,A先任选择一个数,B任选另一个数,如果两数之差被k整除 则A赢否则B赢。1<=n,k<=100
- 题解1。由于范围较小,可以暴力二维枚举i,j 要求(i!=j)的情况下如果有符合的则A赢 复杂度\(O(n^2)\)
- 题解2。可以枚举数组记录每个数mod k,如果最后某个mod个数为1 则A赢
碰到整除相关的,枚举余数往往是个很好的思路。复杂度\(O(n+k)\)
void solve()
{
int n, k; cin >> n >> k;
vector<vector<int>> res(k);
for (int i = 0; i < n; i++) {
int x; cin >> x;
res[x % k].push_back(i + 1);
}
for (int i = 0; i < k; i++) {
if (res[i].size() == 1) {
cout << "yes" << endl;
cout << res[i][0] << endl;
return;
}
}
cout << "no" << endl;
}
给定长为n的初始全0数组,有两种操作。
操作1,任选一个下标把0变成1。
操作2,任选一个子数组,如果子数组两端都是1,且1的总个数>=0的总个数,则把子数组全部变成1。
问把所有元素变成1所需的操作1的最少次数。
显然,操作2是一个倍增操作。而操作1用于给操作2末尾变1用于启动。
操作过程:
开始执行一次操作1 把第一个元素变成1。
保持前缀始终全是1,然后跳过x个0,末尾添加1。把整个部分变成1。
void solve()
{
int n; cin >> n;
int ans = 1;
int cur = 1;
while (cur < n) {
ans++;
cur = (cur + 1) * 2;
}
cout << ans << endl;
}
对于一个长为n的排列p,S(p)表示p所有子数组的最小值的和。
设所有S(p)的最大值为M,所有S(p)=M的排列按字典序排列,输出第k个,如果不足k个,输出-1。
如图,一共有4个有效排列(=10的),所以n=3,k=3时输出[2,3,1]
典型的CF思维题。正难则反。
从大到小处理数组,先放上n,然后逐个处理。
每个数由于比前面的数都小,所以只能放在数组的两端,每次有两种选择方式,所以总共可能有\(2^{n-1}\)种排列。
以n=4为例,开始队列为{4},3放4前面还是后面,形成{3,4}或者{4,3}在字典序中顺序差1。
然后再放2,此时由于3和4的组合有两种,所以2会导致字典序差2。
依次类推,每个数字放前后,取决于当前k的二进制位置上是1还是0。
using i64 = long long;
void solve()
{
i64 n, k; cin >> n >> k;
if (n <= 60 && (1LL << (n - 1)) < k) {
cout << -1 << endl;
return;
}
deque<i64> q{ n };
k--; n--;
while (n > 0)
{
if (k % 2 == 1) q.push_back(n);
else q.push_front(n);
k >>= 1;
n--;
}
for(auto v : q) cout << v << " ";
cout << endl;
}
给定一棵树,节点编号[1-n]。然后要求构建长为n的数组\(array\),\(array[i]\)对应到数上点权。要求:
\(array\)中所有数字不同
\(array[i]<=2n\)
任意边的点权差绝对值不是质数。
首先,不是质数,差为1也是可以的!
结论是必然存在
- 题解1 直接模拟即可。
全局维护当前数字cur,dfs遍历节点
ans[u] = cur++。枚举子节点v的时候,判定cur-ans[u]是否是质数,如果是,就一直增加cur直到不是为止。
事实上,2n的范围很宽松,以至于不用质数那么严谨,只需要找到下一个cur-ans[u]为1或者>2的偶数即可。
严谨的证明不会。可能这就是感觉吧。
void solve()
{
int n; cin >> n;
vector<vector<int>> g(n);
for (int i = 0; i < n - 1; i++) {
int u, v; cin >> u >> v;
u--; v--;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> ans(n);
int cur = 1;
auto dfs = [&](auto&& self, int u, int p)->void {
ans[u] = cur++;
for (auto v : g[u]) {
if (v == p) continue;
int next = cur;
while (true) {
int d = next - ans[u];
if (d == 1) break;
if (d == 2 || d % 2 == 1) next++;
else break;
}
cur = next;
self(self, v, u);
}
};
dfs(dfs, 0, -1);
for (auto v : ans) cout << v << " ";
cout << endl;
}
- 题解2 层序遍历,偶数层设置 2,4,6... 奇数层设置2n,2n-2,2n-4...
这样至多出现一组差为2的点对(u->v) 此时v必然为叶节点,设置ans[v] = ans[v] - 1即可。
给定一棵树,根节点为1,有个机器人从v点出发,向根节点移动,初始step=1,另外有p个硬币。具体移动如下:
如果step为奇数,则直接向根节点移动。
否则要么花费一个硬币,向根节点移动,要么随机向当前节点的任意相邻节点移动。
有q个询问,每次查询如果从v节点开始,有p个硬币,计算最佳策略移动到根节点的期望step。
1<=n,q<=2e3 注意n q的范围
对于从v->1的整条路径,奇数步的位置固定是向上移动的,可以忽略。
对于偶数步,设当前节点v的子节点个数为x,注意加上一个父节点,v的相邻节点有(x+1)个
设f[i]表示节点i的最终期望步数,则有
直接两式相加计算可得\(f[v]=f[u]+2x\)
注意我们有p个硬币,很明显,我们要在分支较多的节点上花费掉硬币。
由于数据范围较小,每个查询可以独立的暴力计算。
void solve()
{
int n, q; cin >> n >> q;
vector<vector<int>> g(n);
for (int i = 0; i < n - 1; i++) {
int u, v; cin >> u >> v;
u--; v--;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> fa(n);
vector<int> size(n);
auto dfs = [&](auto&& self, int u, int p)->void {
fa[u] = p;
for (auto v : g[u]) {
if (v == p) continue;
self(self, v, u);
size[u]++;
}
};
dfs(dfs, 0, -1);
vector<int> ans(q);
for (int i = 0; i < q; i++) {
int v, p; cin >> v >> p; v--;
vector<int> xs;
int step = 1;
while (v != 0) {
if (step % 2 == 0) xs.push_back(size[v]);
v = fa[v];
step++;
}
int ans = step - 1;
sort(xs.begin(), xs.end(), greater<int>());
for (int i = 0; i < xs.size(); i++) {
if (i >= p) ans = ans + xs[i] * 2;
}
cout << ans << endl;
}
}
F. Number of Cubes 这是我能做的题?