AGC010 题解

A - Addition

考虑生成出来的数一定偶数,所以每次要么减少一个偶数,要么增加一个偶数并减少两个奇数,所以有解的充要条件是奇数的个数为偶数。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
    int sig = 1; ll num = 0; char c = getchar();
    while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
    while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
    return num * sig;
}
void Write(ll x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) Write(x / 10);
    putchar((x % 10) ^ 48);
}
int main() {
    int n = Read(), cnt = 0;
    while(n--) cnt ^= Read() & 1;
    printf(cnt ? "NO" : "YES");
}

B - Boxes

首先每次和必定减少 \(\frac{(N + 1)N}{2}\),所以 \(\sum A_i \bmod \frac{(N + 1)N}{2} \not = 0\) 一定无解。
然后我们可以算出操作总次数 \(T\),套路的考虑差分 \(d_i = A_{i \bmod N + 1} - A_{i}\),容易发现每次操作会选定一个 \(t\),令所有 \(d_i\)\(i \not = t\))减少 \(1\),再令 \(d_t\)\(N - 1\)
我们考虑每个位置作为 \(t\) 被选了多少次,设为 \(x\),则:

\[d_i + x(N - 1) - (T - x) = d_i + xN - T = 0 \]

所以:

\[x = \frac{T - d_i}{N} \]

则只需判断 \(d_i\) 是不是非负整数。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
    int sig = 1; ll num = 0; char c = getchar();
    while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
    while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
    return num * sig;
}
void Write(ll x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) Write(x / 10);
    putchar((x % 10) ^ 48);
}
const int N = 200005;
int n, a[N], d[N];
int main() {
    int i; ll sum = 0; n = Read();
    for(i = 1; i <= n; i++) a[i] = Read(), sum += a[i];
    if(sum % (1ll * n * (n + 1) / 2)) { printf("NO"), exit(0); }
    sum /= 1ll * n * (n + 1) / 2;
    for(i = 1; i <= n; i++) {
        d[i] = a[i % n + 1] - a[i];
        if(d[i] > sum || (sum - d[i]) % n) { printf("NO"); exit(0); }
    }
    printf("YES");
}

C - Cleaning

随便钦定一个度数非 \(1\) 的结点为根,假设结点 \(u\) 的儿子都是叶子结点,考虑选定 \(x\) 个石子上传至祖先结点与其他石子结合形成路径,\(2y\) 个石子直接在 \(u\) 结点结合形成路径,设儿子结点的石子个数和为 \(S\),有方程:

\[\begin{cases} x + 2y = S \\ x + y = a_u \end{cases}\]

可以解出 \(x, y\) 的值,然后可以将结点 \(u\) 视为叶子结点,有 \(x\) 个石子,所以从下往上递归就可以了。
现在的问题就变成了,对于儿子结点的石子分布情况,判断能否凑成 \(y\) 条路径。
考虑儿子结点的石头个数组成的序列 \(\{b_i\}\),则若 \(\max b_i \le \frac{1}{2}\sum b_i\),那么我们每次取两个最大的 \(b_i\),分别减 \(1\)(这样上述条件依然成立),一定能匹配 \(\lfloor\frac{\sum b_i}{2}\rfloor\) 条路径,否则只能匹配 \(\sum b_i - \max b_i\) 条路径,判断一下就可以了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
    int sig = 1; ll num = 0; char c = getchar();
    while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
    while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
    return num * sig;
}
void Write(ll x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) Write(x / 10);
    putchar((x % 10) ^ 48);
}
const int N = 100005;
int n;
ll a[N];
vector<int> e[N];
void Dfs(int u, int fa) {
    ll s = 0, d = 0, r;
    if(e[u].size() == 1) return ;
    for(auto v : e[u]) if(v != fa) Dfs(v, u), s += a[v], d = max(d, a[v]);
    r = (d > s / 2) ? s - d : s / 2ll;
    if(a[u] < s - r || a[u] > s) printf("NO"), exit(0);
    a[u] = 2ll * a[u] - s;
}
int main() {
    int i, rt; n = Read();
    for(i = 1; i <= n; i++) a[i] = Read();
    for(i = 1; i < n; i++) { int u = Read(), v = Read(); e[u].emplace_back(v), e[v].emplace_back(u); }
    if(n == 2) printf(a[1] == a[2] ? "YES" : "NO"), exit(0);
    for(i = 1; i <= n; i++) if(e[i].size() > 1) { rt = i; break; }
    Dfs(rt, 0), printf(a[rt] ? "NO" : "YES");
}

D - Decrementing

不考虑每次操作后要除以所有数的 \(\gcd\) 的限制,那么先手赢当且仅当所有数的和为奇数。
假设一次操作后,所有数的 \(\gcd\) 是奇数,那么所有数之和的奇偶性不变,只需考虑 \(\gcd\) 为偶数的情况。
首先解决一个基本的 corner case:

  • 若数列中至少有一个 \(1\),那么只需考虑所有数的和的奇偶性。

去掉这个 corner case 之后,我们发现问题的瓶颈在于偶数个数,因为每次操作(不考虑除以所有数 \(\gcd\) 这一步)必然改变偶数个数的奇偶性:

  • 若偶数个数为奇数,那么先手必然可以通过恰当的策略,使得对手侧的序列中偶数个数为偶数(操作一个任意奇数,非 corner case 保证了一定存在这个数),那么先手必胜;
  • 否则考虑偶数个数为偶数,假设还剩至少 \(2\) 个奇数,先手无论怎么操作,都会导致对手侧偶数个数为奇数,就留给对手一个先手必胜的情况;
  • 否则,考虑先手唯一可能胜利的方法是操作那个奇数,不然会出现和上文相同的情况,所以递归下去做就可以了,递归层数是 \(O(\log V)\) 的。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
    int sig = 1; ll num = 0; char c = getchar();
    while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
    while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
    return num * sig;
}
void Write(ll x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) Write(x / 10);
    putchar((x % 10) ^ 48);
}
const int N = 100005;
int n, a[N];
int main() {
    int i, cnt = 0; bool o = false; n = Read();
    for(i = 1; i <= n; i++) a[i] = Read();
    while(true) {
        bool b = false;
        for(i = 1; i <= n; i++) b |= a[i] == 1, cnt += (a[i] % 2 == 0);
        if(b || (cnt & 1)) printf((cnt & 1) ^ o ? "First" : "Second"), exit(0);
        int g = 0;
        for(i = 1; i <= n; i++) {
            if(a[i] & 1) {
                if(b) printf(o ? "First" : "Second"), exit(0);
                else a[i]--, b = true;
            }
            g = (g ? __gcd(g, a[i]) : a[i]);
        }
        for(i = 1; i <= n; i++) a[i] /= g;
        o ^= 1, cnt = 0;
    }
}

E - Rearranging

考虑不互质的两个数不可跨越,我们先钦定这类数的位置。建边,若两个数不互质则连一条边。考虑先手实际上是在给这些边定向,使得图为一个 DAG,每次贪心的加入当前最小点,并将当前与它相连的所有点中选一个最小的未被选的点,连边并继续递归,否则可以回溯。
可能需要说明一下这为什么是对的,考虑最小点一定是最先被加入的,每次这个最小点把整个图分成了若干个连通块,连通块之间没有影响,而对于一个连通块而言,我们不能选择不与当前最小点相连的点,因为这样后手一定可以将这个点与前面的点互换导致不优,所以只能选择与最小点相连的最小的点。
而对于后手的策略,我们直接对原图进行拓补排序,将队列换成大根堆就可以了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
    int sig = 1; ll num = 0; char c = getchar();
    while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
    while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
    return num * sig;
}
void Write(ll x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) Write(x / 10);
    putchar((x % 10) ^ 48);
}
const int N = 3005;
int n, a[N];
vector<int> e[N];
bool Dfs(int u, int fa) { bool b = false; for(auto v : e[u]) if(v != fa && a[v] < a[u]) b |= !Dfs(v, u); return b; }
int main() {
    int i; n = Read();
    for(i = 1; i <= n; i++) a[i] = Read();
    for(i = 1; i < n; i++) { int u = Read(), v = Read(); e[u].emplace_back(v), e[v].emplace_back(u); }
    for(i = 1; i <= n; i++) if(Dfs(i, 0)) Write(i), putchar(' ');
}

F - Tree Game

对于相邻的点 \(u, v\),假设当前在 \(u\)\(a_v \ge a_u\),那么先手走到 \(v\) 一定不优,因为后手可以走到 \(u\),导致仍有 \(a_v \ge a_u\),先手若重复上述操作一定落败。
所以先手只能走到 \(a_v < a_u\) 的点 \(v\),由于 \(a_u - 1 \ge a_v\),所以先手不能走回父亲,于是转化为了子问题,递归下去,判断先手的胜负情况是容易的。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
    int sig = 1; ll num = 0; char c = getchar();
    while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
    while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
    return num * sig;
}
void Write(ll x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) Write(x / 10);
    putchar((x % 10) ^ 48);
}
const int N = 3005;
int n, a[N];
vector<int> e[N];
bool Dfs(int u, int fa) { bool b = false; for(auto v : e[u]) if(v != fa && a[v] < a[u]) b |= !Dfs(v, u); return b; }
int main() {
    int i; n = Read();
    for(i = 1; i <= n; i++) a[i] = Read();
    for(i = 1; i < n; i++) { int u = Read(), v = Read(); e[u].emplace_back(v), e[v].emplace_back(u); }
    for(i = 1; i <= n; i++) if(Dfs(i, 0)) Write(i), putchar(' ');
}
posted @ 2025-07-08 21:03  Include_Z_F_R_qwq  阅读(24)  评论(1)    收藏  举报