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\) 是权值。

关于构造,考虑在重心处构造即可,让每棵子树都不并列。(好像是经典题了)

那哈密顿路径就是哈密顿回路去掉一条边。显然我们去掉所有可能性中能让答案最大的边,也就是边权尽量小。

可以看出本题中重心是很重要的,众所周知,有两类重心:

  1. 重心在边上,也就是一条边去掉后可以把两侧分成大小恰好为 \(N / 2\) 的两棵子树:
    这条边此时显然最多只能经过 \(N - 1\) 次了,那么答案肯定不超过 \(\sum 2 S_i C_i - C_v\)(其中 \(v\) 就是这条边)。
    而这个又是能够构造的,本文略。
  2. 重心在点上:
    此时考虑与该点相邻的所有边 \(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;
}
posted @ 2020-08-10 02:32  粉兔  阅读(670)  评论(0编辑  收藏  举报