【AtCoder】AtCoder Regular Contest 096 题解

本文提供了HalfandHalf、StaticSushi、EverythingonIt及SweetAlchemy四道比赛题目的详细解答思路与代码实现,涵盖贪心算法、动态规划、组合数学等核心算法思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【比赛链接】

【题解链接】

【C】Half and Half

【思路要点】

  • 每次考虑购买两个AB披萨是否会节省,如果会,则购买,并重复这个考虑的过程。
  • 否则结束这个考虑的过程,购买剩余所需的A披萨和B披萨。
  • 时间复杂度\(O(min\{X,Y\})\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int main() {
	int a, b, c, x, y, ans = 0;
	read(a), read(b), read(c), read(x), read(y);
	while (true) {
		int tmp = 0;
		if (x) tmp += a;
		if (y) tmp += b;
		if (tmp > 2 * c) {
			ans += 2 * c;
			if (x) x--;
			if (y) y--;
		} else break;
	}
	ans += a * x + b * y;
	writeln(ans);
	return 0;
}

【D】Static Sushi

【思路要点】

  • 最优的路线应当形如:向起点的一端走到某寿司处(或不走),回到起点,并走到另一端某寿司处,离开餐馆。
  • 可以看做向一个方向移动消耗为2,向另一个方向移动消耗为1,两个方向吃的寿司不能够有交集。
  • 枚举消耗为2的方向以及在该方向上走到的寿司,维护消耗为1方向上收益-消耗的前缀最大值即可统计答案。
  • 时间复杂度\(O(N)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const long long INF = 1e18;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n; long long ans, c;
long long x[MAXN], v[MAXN], pre[MAXN], sum[MAXN];
void solve(long long c) {
	for (int i = 1; i <= n; i++) {
		sum[i] = sum[i - 1] + v[i];
		pre[i] = max(pre[i - 1], sum[i] - x[i]);
	}
	long long now = 0;
	for (int i = n; i >= 1; i--) {
		chkmax(ans, now + pre[i]);
		now += v[i] - 2 * (c - x[i]);
		c = x[i];
	}
}
int main() {
	read(n), read(c);
	for (int i = 1; i <= n; i++)
		read(x[i]), read(v[i]);
	solve(c);
	for (int i = 1; i <= n; i++)
		x[i] = c - x[i];
	reverse(x + 1, x + n + 1);
	reverse(v + 1, v + n + 1);
	solve(c);
	writeln(ans);
	return 0;
}

【E】Everything on It

【思路要点】

  • 由容斥原理,有\(Ans=\sum_{i=0}^{N}(-1)^i*\binom{N}{i}*cnt_i\),其中\(cnt_i\)为1到\(i\)号元素只选取至多一个的方案数。
  • 对于\(i+1\)号到\(N\)号物品,我们没有做额外的限制,因此对于与集合\(\{1,2,...,i\}\)交集相同的物品我们可以视为一种物品,每一种物品有\(2^{N-i}\)个。
  • 显然除了与集合\(\{1,2,...,i\}\)交集为空的一组物品以外,每组物品最多选取一个。
  • 我们可以将这个过程看做将1到\(i\)号元素分成若干个无序的组,每一个元素至多被分在一个组中,也可以不分在任何组中,每一组中的元素是在同一次物品选取中被选中的。
  • 记将1到\(i\)号元素分成恰好\(j\)个无序的组的方案数为\(dp_{i,j}\),那么\(dp_{i,j}\)对\(cnt_i\)的贡献应当为\(dp_{i,j}*(2^{N-i})^j*2^{2^{N-i}}\)。
  • 剩余的问题就是如何计算\(dp_{i,j}\),考虑第\(i\)个元素的归属:它可以自成一组,也可以加入到之前已有的一个组中或不加入任何组,因此\(dp_{i,j}=dp_{i-1,j-1}+dp_{i-1,j}*(j+1)\)。
  • 时间复杂度\(O(N^2)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, ans, P;
int fac[MAXN], inv[MAXN], dp[MAXN][MAXN];
int c(int x, int y) {
	if (y > x) return 0;
	else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
	x = (x + y) % P;
}
int power(int x, int y, int P) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2, P);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int getdp(int x) {
	int mul = power(2, power(2, n - x, P - 1), P);
	int chs = power(2, n - x, P), now = 1, ans = 0;
	for (int i = 0; i <= x; i++) {
		update(ans, 1ll * dp[x][i] * now % P);
		now = 1ll * now * chs % P;
	}
	return 1ll * mul * ans % P;
}
int main() {
	read(n), read(P);
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = 1ll * fac[i - 1] * i % P;
	inv[n] = power(fac[n], P - 2, P);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
	dp[0][0] = 1;
	for (int i = 1; i <= n; i++)
	for (int j = 0; j <= i; j++) {
		if (j) dp[i][j] += dp[i - 1][j - 1];
		update(dp[i][j], dp[i - 1][j] * (j + 1ll) % P);
	}
	for (int i = 0; i <= n; i++)
		if (i & 1) update(ans, 1ll * (P - c(n, i)) * getdp(i) % P);
		else update(ans, 1ll * c(n, i) * getdp(i) % P);
	writeln(ans);
	return 0;
}

【F】Sweet Alchemy

【思路要点】

  • 我们对\(c_i\)进行差分,令\(d_i=c_i-c_{p_i}\),那么原题中对\(c_i\)的限制可以等价地表示为\(0≤d_i≤D(i=2,3,...,N)\)。
  • \(d_i\)每增加1,意味着我们需要购买\(i\)子树内所有物品各一份。
  • 现在问题等价地转化为了背包问题:\(N\)种物品,每种物品体积为\(sum_i\),价值为\(size_i\)且除了第一种物品以外其余物品有数量限制\(D\),求\(X\)体积的背包能够容纳的物品的最大价值总和。(其中\(sum_i\)为\(i\)子树内所有物品的代价总和,\(size_i\)为\(i\)子树的大小)
  • 注意到\(size_i≤N≤50\),考虑从\(size_i\)入手分析。
  • 有一种直观的贪心:按照物品的“性价比”从高到低选取,其中“性价比”为价值与体积的比值。
  • 由于物品不能够分割,这样的贪心不是完全正确的,但由这个思路,我们发现,若物品\(i\)的性价比高于物品\(j\),且物品\(j\)选取了\(size_i\)个,我们不妨将这些物品替换为\(size_j\)个物品\(i\),这样一来,物品的价值不变,但体积就减少了。
  • 因此,低“性价比”的物品至多会被选取\(N\)件,我们拿出每种物品中\(Min\{N,D\}\)件,这样拿出物品的总价值不会超过\(O(N^3)\)。进行简单DP,求出\(dp_i\),表示选取价值为\(i\)的物品所占的最少体积。
  • 之后枚举在DP中选取物品的价值,并对剩余物品执行上述贪心,取最优解即可。
  • 时间复杂度\(O(N^5)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
const int MCNT = 125005;
const int INF = 1e9 + 10;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
long long limit, sum[MAXN];
long long dp[MAXN][MCNT];
int n, d, fa[MAXN], size[MAXN], pos[MAXN];
bool cmp(int x, int y) {
	return sum[x] * size[y] < sum[y] * size[x];
}
int main() {
	read(n), read(limit), read(d);
	for (int i = 1; i <= n; i++) {
		read(sum[i]);
		if (i != 1) read(fa[i]);
	}
	int cnt = 0;
	for (int i = n; i >= 1; i--) {
		sum[fa[i]] += sum[i];
		size[fa[i]] += ++size[i];
		cnt += size[i] * n;
		pos[i] = i;
	}
	sort(pos + 1, pos + n + 1, cmp);
	for (int i = 1; i <= cnt; i++)
		dp[0][i] = INF;
	int tmp = min(n, d);
	for (int i = 1; i <= n; i++) {
		memcpy(dp[i], dp[i - 1], sizeof(dp[i]));
		for (int j = 0; j <= cnt; j++)
		for (int k = 1; k <= tmp && j - k * size[i] >= 0; k++)
			chkmin(dp[i][j], dp[i - 1][j - k * size[i]] + k * sum[i]);
	}
	int ans = 0;
	for (int i = 0; i <= cnt; i++) {
		if (dp[n][i] > limit) continue;
		int tans = i, lft = limit - dp[n][i];
		for (int j = 1; j <= n; j++) {
			int tmp = pos[j], used = min(1ll * max(d - n, 0), lft / sum[tmp]);
			if (tmp == 1) used = lft / sum[tmp];
			lft -= sum[tmp] * used;
			tans += size[tmp] * used;
		}
		chkmax(ans, tans);
	}
	writeln(ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值