【比赛链接】
【题解链接】
【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; }