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\) 是不是非负整数。
#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\),有方程:
可以解出 \(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(' ');
}

浙公网安备 33010602011771号