AtCoder Grand Contest 018
题目传送门:AtCoder Grand Contest 018。
A - Getting Difference
当 \(\gcd(A_1, A_2, \ldots , A_N) \mid K\) 且 \(K \le \max(A_1, A_2, \ldots , A_N)\) 时输出 POSSIBLE
。
#include <cstdio>
#include <algorithm>
int N, K, g, mx;
int main() {
scanf("%d%d", &N, &K);
for (int i = 1, x; i <= N; ++i)
scanf("%d", &x),
g = std::__gcd(g, x),
mx = std::max(mx, x);
puts(K <= mx && K % g == 0 ? "POSSIBLE" : "IMPOSSIBLE");
return 0;
}
B - Sports Festival
我们假设一开始所有项目都举办了,此时考虑一个当前时刻参与人数最多的项目。
如果这个项目不去掉,则答案永远不会比这个项目此时的参与人数少。那么必须去掉它。
一直这样去掉,直到只剩一个了,在这之间求最小值即可。
#include <cstdio>
const int MN = 305, MM = 305;
int N, M, A[MN][MM], cho[MM], p[MN], buk[MM], Ans;
int main() {
scanf("%d%d", &N, &M), Ans = N;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j)
scanf("%d", &A[i][j]);
for (int j = 1; j <= M; ++j) cho[j] = 1;
for (int i = 1; i <= N; ++i) p[i] = 1;
for (int k = 1; k <= M; ++k) {
for (int j = 1; j <= M; ++j) buk[j] = 0;
for (int i = 1; i <= N; ++i) {
while (!cho[A[i][p[i]]]) ++p[i];
++buk[A[i][p[i]]];
}
int mxi = 0;
for (int j = 1; j <= M; ++j)
if (buk[mxi] < buk[j]) mxi = j;
if (Ans > buk[mxi]) Ans = buk[mxi];
cho[mxi] = 0;
}
printf("%d\n", Ans);
return 0;
}
C - Coins
我们假装全部选 \(A\),然后把 \(B, C\) 都减去 \(A\)。
那就是要选 \(Y\) 个 \(B\) 和 \(Z\) 个 \(C\),最大化收益。
那再对所有数对 \((B, C)\) 按照 \(B\) 的值从大到小排序。
那么此时我们钦点了一些位置选了 \(C\),则就是尽量前面的没有选 \(C\) 的位置选了 \(B\)。
那么我们在 \(Y \sim Y + Z\) 之间钦点一个位置,它是最后一个 \(B\) 的选取位置。
那么现在又变成了:
先钦点这个位置之前的全选 \(B\),要在左边换一些变成 \(C\),剩下恰好 \(Y\) 个 \(B\);
先钦点右边全没选,也要在右边换一些变成 \(C\),剩下恰好 \(X\) 个位置没选。
这个其实是经典题了,左侧的问题,就用大小 \(= Y\) 的一个优先队列维护。
右侧就是用 \(= X\) 的优先队列维护。
时间复杂度为 \(\mathcal O (N \log N)\)。
#include <cstdio>
#include <algorithm>
#include <queue>
typedef long long LL;
const LL Inf = 0x3f3f3f3f3f3f3f3f;
const int MN = 100005;
int N, X, Y, Z, B[MN], C[MN], P[MN];
LL Pre[MN], Suf[MN];
std::priority_queue<LL> pq;
LL Sum, Ans;
int main() {
scanf("%d%d%d", &X, &Y, &Z), N = X + Y + Z, Ans = -Inf;
for (int i = 1; i <= N; ++i) {
int a;
scanf("%d%d%d", &a, &B[i], &C[i]);
B[i] -= a, C[i] -= a, P[i] = i;
Sum += a;
}
std::sort(P + 1, P + N + 1, [](int i, int j) { return B[i] > B[j]; });
for (int i = 1; i <= Y + Z; ++i) {
Pre[i] = Pre[i - 1] + B[P[i]];
pq.push(C[P[i]] - B[P[i]]);
if ((int)pq.size() > Y) Pre[i] += pq.top(), pq.pop();
}
while (!pq.empty()) pq.pop();
for (int i = N; i > Y; --i) {
Suf[i] = Suf[i + 1];
pq.push(C[P[i]]);
if ((int)pq.size() > X) Suf[i] += pq.top(), pq.pop();
}
for (int i = Y; i <= Y + Z; ++i)
Ans = std::max(Ans, Pre[i] + Suf[i + 1]);
printf("%lld\n", Sum + Ans);
return 0;
}
D - Tree and Hamilton Path
这是哈密顿路径,如果是哈密顿回路呢?
一条边能在哈密顿回路中被经过的次数,显然有一个上界是 \(2 S_i C_i\),其中 \(S_i\) 是第 \(i\) 条边两端的子树的较小大小,\(C_i\) 是权值。
关于构造,考虑在重心处构造即可,让每棵子树都不并列。(好像是经典题了)
那哈密顿路径就是哈密顿回路去掉一条边。显然我们去掉所有可能性中能让答案最大的边,也就是边权尽量小。
可以看出本题中重心是很重要的,众所周知,有两类重心:
- 重心在边上,也就是一条边去掉后可以把两侧分成大小恰好为 \(N / 2\) 的两棵子树:
这条边此时显然最多只能经过 \(N - 1\) 次了,那么答案肯定不超过 \(\sum 2 S_i C_i - C_v\)(其中 \(v\) 就是这条边)。
而这个又是能够构造的,本文略。 - 重心在点上:
此时考虑与该点相邻的所有边 \(e_1, e_2, \ldots , e_k\)。
我们可以证明这些边之中必须有一条边相比哈密顿回路时的情况,少被经过一次。
同理我们选择一个 \(C\) 值最小的那条边删掉即可。
#include <cstdio>
#include <algorithm>
#include <vector>
typedef long long LL;
const int MN = 100005;
int N;
std::vector<std::pair<int, int>> G[MN];
LL Ans; int WTF;
int siz[MN], rt, rts;
void DFS(int u, int p, int x) {
siz[u] = 1;
int mxs = 0;
for (auto e : G[u]) if (e.first != p) {
int v = e.first;
DFS(v, u, e.second), siz[u] += siz[v];
mxs = std::max(mxs, siz[v]);
}
if (2 * siz[u] == N) WTF = x;
Ans += std::min(siz[u], N - siz[u]) * (LL)x;
mxs = std::max(mxs, N - siz[u]);
if (rts > mxs) rt = u, rts = mxs;
}
int main() {
scanf("%d", &N);
for (int i = 1, x, y, z; i < N; ++i) {
scanf("%d%d%d", &x, &y, &z);
G[x].push_back({y, z});
G[y].push_back({x, z});
}
rts = N, DFS(1, 0, 0);
if (!WTF) {
WTF = 0x3f3f3f3f;
for (auto e : G[rt]) WTF = std::min(WTF, e.second);
}
printf("%lld\n", Ans * 2 - WTF);
return 0;
}
E - Sightseeing Plan
如果固定中转点。那么方案数就是每个起点到中转点,和每个终点到中转点,的方案数的乘积。
那么一个矩形的起点,到一个点,的方案数。其实就是杨辉三角上一个菱形的和。
其实就是四个点到一个点的方案数(但是系数是两个 \(+1\) 两个 \(-1\))。
那么现在就转化成 \(4 \times 4 = 16\) 种情况的,起点和终点都是唯一的,但是中转点是在一个区域内的情况。
此时就相当于:对每个起点到终点的每条路径,把经过那个区域的长度求和。
此时又是一个很重要的转换:经过那个区域的长度转化成,离开那个区域时的坐标,减去进入那个区域时的坐标(曼哈顿距离)。
那么我们就可以枚举离开那个区域时的位置,求出满足的路径数,乘以曼哈顿坐标贡献给答案。
进入那个区域的情况同理。
预处理组合数,时间复杂度就是坐标范围。
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int Mod = 1000000007;
const int MV = 2000005;
inline int qPow(int b, int e) {
int a = 1;
for (; e; e >>= 1, b = (LL)b * b % Mod)
if (e & 1) a = (LL)a * b % Mod;
return a;
}
inline int gInv(int b) { return qPow(b, Mod - 2); }
int Fac[MV], iFac[MV];
inline void Init(int N) {
Fac[0] = 1;
for (int i = 1; i <= N; ++i) Fac[i] = (LL)Fac[i - 1] * i % Mod;
iFac[N] = gInv(Fac[N]);
for (int i = N; i >= 1; --i) iFac[i - 1] = (LL)iFac[i] * i % Mod;
}
inline int Binom(int N, int M) {
if (M < 0 || M > N) return 0;
return (LL)Fac[N] * iFac[M] % Mod * iFac[N - M] % Mod;
}
inline int Calc(int N, int M) {
return Binom(N + M, N);
}
int X1, X2, X3, X4, X5, X6;
int Y1, Y2, Y3, Y4, Y5, Y6;
int sx[4], sy[4], tx[4], ty[4];
int Ans;
int main() {
Init(2000000);
scanf("%d%d%d%d%d%d", &X1, &X2, &X3, &X4, &X5, &X6);
scanf("%d%d%d%d%d%d", &Y1, &Y2, &Y3, &Y4, &Y5, &Y6);
sx[0] = X1 - 1, sy[0] = Y1 - 1;
sx[1] = X1 - 1, sy[1] = Y2;
sx[2] = X2, sy[2] = Y2;
sx[3] = X2, sy[3] = Y1 - 1;
tx[0] = X6 + 1, ty[0] = Y6 + 1;
tx[1] = X6 + 1, ty[1] = Y5;
tx[2] = X5, ty[2] = Y5;
tx[3] = X5, ty[3] = Y6 + 1;
for (int sk = 0; sk < 4; ++sk) {
for (int tk = 0; tk < 4; ++tk) {
int spx = sx[sk], spy = sy[sk];
int tpx = tx[tk], tpy = ty[tk];
int coef = (sk ^ tk) & 1 ? -1 : 1;
int Sum = 0;
for (int i = X3; i <= X4; ++i) {
Sum = (Sum + (LL)Calc(i - spx, Y3 - 1 - spy) * Calc(tpx - i, tpy - Y3) % Mod * -(i + Y3 - 1)) % Mod;
Sum = (Sum + (LL)Calc(i - spx, Y4 - spy) * Calc(tpx - i, tpy - Y4 - 1) % Mod * (i + Y4)) % Mod;
}
for (int j = Y3; j <= Y4; ++j) {
Sum = (Sum + (LL)Calc(X3 - 1 - spx, j - spy) * Calc(tpx - X3, tpy - j) % Mod * -(X3 - 1 + j)) % Mod;
Sum = (Sum + (LL)Calc(X4 - spx, j - spy) * Calc(tpx - X4 - 1, tpy - j) % Mod * (X4 + j)) % Mod;
}
Ans = (Ans + coef * Sum) % Mod;
}
}
printf("%d\n", (Ans + Mod) % Mod);
return 0;
}
F - Two Trees
首先声明 \(X\) 值只有可能取到 \(\{-1, 0, 1\}\)。
我们可以根据孩子个数确定 \(X_i\) 的奇偶性。如果两棵树确定的某个点的奇偶性不同,显然就输出 IMPOSSIBLE
。
接下来的构造说明了除了上述情况都是 POSSIBLE
的。
构造一张图,这张图首先包含了原先的两棵树。
加一个超级根,连接两棵树的树根。
那么此时树上的每个点 \(u\),\(X_u\) 的奇偶性是与此时这个点的度数的奇偶性相同的。
对于度数为奇数的点,在两棵树的对应节点之间连边。
现在所有点都是偶数度数的了,而且整个图连通,那么就存在欧拉回路,给欧拉回路随便定个向。
那么某个节点的 \(X_i\) 是 \(+1\) 还是 \(-1\) 就取决于两棵树对应节点之间的连边的方向了。
容易验证此时每个子树都满足条件。
#include <cstdio>
#include <vector>
const int MN = 100005, ME = 300005;
int N, A[MN], B[MN], R1, R2;
int deg1[MN], deg2[MN];
int E, eu[ME], ev[ME], dir[ME];
std::vector<int> G[MN * 2];
void DFS(int u) {
while (!G[u].empty()) {
int e = G[u].back();
G[u].pop_back();
if (!dir[e]) {
int v = eu[e] ^ ev[e] ^ u;
dir[e] = u == eu[e] ? 1 : 2;
DFS(v);
}
}
}
int Ans[MN];
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
for (int i = 1; i <= N; ++i) scanf("%d", &B[i]);
for (int i = 1; i <= N; ++i) {
if (A[i] != -1) {
++deg1[A[i]], ++deg1[i];
++E, eu[E] = A[i], ev[E] = i;
G[A[i]].push_back(E);
G[i].push_back(E);
} else R1 = i;
if (B[i] != -1) {
++deg2[B[i]], ++deg2[i];
++E, eu[E] = N + B[i], ev[E] = N + i;
G[N + B[i]].push_back(E);
G[N + i].push_back(E);
} else R2 = i;
}
++deg1[R1], ++deg2[R2];
++E, eu[E] = N + N + 1, ev[E] = R1;
G[N + N + 1].push_back(E), G[R1].push_back(E);
++E, eu[E] = N + N + 1, ev[E] = N + R2;
G[N + N + 1].push_back(E), G[N + R2].push_back(E);
for (int i = 1; i <= N; ++i) if ((deg1[i] ^ deg2[i]) & 1) return puts("IMPOSSIBLE"), 0;
puts("POSSIBLE");
for (int i = 1; i <= N; ++i) if (deg1[i] & 1) {
++E, eu[E] = i, ev[E] = N + i;
G[i].push_back(E), G[N + i].push_back(E);
}
DFS(N + N + 1);
for (int i = E; i > 2 * N; --i)
Ans[eu[i]] = dir[i] == 1 ? 1 : -1;
for (int i = 1; i <= N; ++i) printf("%d%c", Ans[i], " \n"[i == N]);
return 0;
}