Record
[USACO20JAN] Time is Mooney G
简单动态规划。
由于 \(T\) 天会花费 \(C \times T^2\),所以每多一天的花费为 \(C \times (i^2 - (i-1)^2) = C \times (2i - 1)\)。因此,当 \(C \times (2i - 1) \ge max\{m_i\}\) 时就说明再怎么走也无法更优,于是结束程序。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int n, m, ans, f[N][2], a[N], c, ma;
vector<int> e[N];
int main() {
scanf("%d %d %d", &n, &m, &c);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]), ma = max(ma, a[i]);
for (int i = 1, u, v; i <= m; i++) {
scanf("%d %d", &u, &v);
e[v].push_back(u);
}
memset(f, -0x3f, sizeof(f));
f[1][0] = 0;
for (int T = 1; c * (2 * T - 1) <= ma; T++) {
for (int i = 1; i <= n; i++) {
f[i][T & 1] = -0x3f3f3f3f;
for (auto j : e[i])
if (f[j][T - 1 & 1] != -0x3f3f3f3f)
f[i][T & 1] = max(f[i][T & 1], f[j][T - 1 & 1] + a[i]);
}
ans = max(ans, f[1][T & 1] - c * T * T);
}
printf("%d\n", ans);
return 0;
}
[USACO20JAN] Farmer John Solves 3SUM G
发现不能直接通过一个 dp 求出 \(l \sim r\) 的答案。
但我们可以先固定三元组的第一个元素为左端点 \(i\),再枚举右端点 \(j\),计算得到包含 \(i\) 和 \(j\) 且以它们为左右端点的三元组数量。
现在,就可以通过区间dp求出答案:\(ans_{l, r} = cnt_{l, r} + ans_{l + 1, r} + ans_{l, r - 1} - ans_{l + 1, r - 1}\)。因为 \(ans_{l + 1, r} + ans_{l, r - 1} - ans_{l + 1, r - 1}\) 不包含同时以 \(l\) 和 \(r\) 为端点的情况,所以要加上 \(cnt_{l, r}\)。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5, M = 3e6 + 5, NUM = 1e6;
int n, q, cnt[N][N], a[N], s[M];
long long f[N][N];
int main() {
scanf("%d %d", &n, &q);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]), a[i] += NUM;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (j >= i + 2 && 3 * NUM >= a[i] + a[j] && NUM <= a[i] + a[j])
cnt[i][j] = s[3 * NUM - a[i] - a[j]];
s[a[j]]++;
}
for (int j = i + 1; j <= n; j++)
s[a[j]]--;
}
for (int l = 2; l <= n; l++)
for (int i = 1; i + l - 1 <= n; i++) {
int j = i + l - 1;
f[i][j] = f[i + 1][j] + f[i][j - 1] - f[i + 1][j - 1] + cnt[i][j];
}
while (q--) {
int l, r;
scanf("%d %d", &l, &r);
printf("%lld\n", f[l][r]);
}
return 0;
}
[USACO20JAN] Springboards G
发现答案等于 \(2N\) 减去跳过的距离,所以求最短走动距离只用求最多能跳过多少距离。dp 即可。
把每一组点拆开,然后从小到大排序,然后倒序枚举。
-
当枚举到的点是经过传送到达的点时,记录 \(y\) 坐标不比它小的点中最多能跳过多少距离。
-
否则,把它传送到的点所记录的答案加上这次传送所到达的距离与 \(y\) 坐标不比它小的点中最多能跳过多少距离比大小,取较大值。
至于如何快速求 \(y\) 坐标不比它小的点中最多能跳过多少距离,树状数组即可。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, m, bit[N], ans, b[N], val[N];
struct node {
int x, y, id, quantity;
bool flag;
inline node() {}
inline bool operator<(node X) const { return x != X.x ? x < X.x : y < X.y; }
} a[N];
inline int lowbit(int x) { return x & (-x); }
inline void update(int x, int y) {
while (x <= m * 2)
bit[x] = max(bit[x], y), x += lowbit(x);
}
inline int query(int x) {
int ans = 0;
while (x)
ans = max(ans, bit[x]), x -= lowbit(x);
return ans;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d %d %d", &a[i * 2 - 1].x, &a[i * 2 - 1].y, &a[i * 2].x, &a[i * 2].y);
a[i * 2].flag = true, b[i * 2 - 1] = a[i * 2 - 1].y, b[i * 2] = a[i * 2].y;
a[i * 2 - 1].id = a[i * 2].id = i;
a[i * 2 - 1].quantity = a[i * 2].quantity = a[i * 2].x - a[i * 2 - 1].x + a[i * 2].y - a[i * 2 - 1].y;
}
sort(b + 1, b + m * 2 + 1), sort(a + 1, a + m * 2 + 1);
for (int i = m * 2; i; i--) {
a[i].y = 2 * m - (lower_bound(b + 1, b + m * 2 + 1, a[i].y) - b) + 1;
if (a[i].flag)
val[a[i].id] = query(a[i].y);
else {
val[a[i].id] = max(val[a[i].id] + a[i].quantity, query(a[i].y));
update(a[i].y, val[a[i].id]);
ans = max(ans, val[a[i].id]);
}
}
printf("%d\n", n * 2 - ans);
return 0;
}
[USACO20FEB] Timeline G
直接查分约束即可。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m, c, dis[N];
bool vis[N];
deque<int> q;
vector<pair<int, int>> e[N];
inline void spfa(int s) {
q.push_back(s), vis[s] = true;
fill(dis + 1, dis + n + 1, -0x3f3f3f3f);
while (!q.empty()) {
int u = q.front();
q.pop_front(), vis[u] = false;
for (auto i : e[u]) {
int v = i.first, w = i.second;
if (dis[v] < dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) {
if (q.empty() || dis[v] >= dis[q.front()])
q.push_front(v);
else
q.push_back(v);
vis[v] = true;
}
}
}
}
}
int main() {
scanf("%d %d %d", &n, &m, &c);
for (int i = 1, start; i <= n; i++) {
scanf("%d", &start);
e[0].emplace_back(i, start);
e[i].emplace_back(0, -m);
}
for (int i = 1, u, v, w; i <= c; i++) {
scanf("%d %d %d", &u, &v, &w);
e[u].emplace_back(v, w);
}
spfa(0);
for (int i = 1; i <= n; i++)
printf("%d\n", dis[i]);
return 0;
}
[USACO20FEB] Help Yourself G
感觉求并不是很容易,于是想怎么可以求交来解决。
于是发现,当有 \(x\) 个连通块时,一定有 \(x + 1\) 个连续区间满足每一个区间都未被覆盖。
所以将 \(l \sim r\) 的区间处理为 \(0 \sim l - 1\) 和 \(r + 1 \sim 2n + 1\) 即可。
注意:由于此题并不是在离散点集上处理,所以当有一个区间的 \(r\) 大于等于另一区间的 \(l\) 时才满足条件。
code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5, Mod = 1e9 + 7;
int n, m, ans, Pow[N], bit[N << 1];
pair<int, int> a[N << 1];
inline int lowbit(int x) { return x & (-x); }
inline void update(int x, int y) {
x++;
while (x <= 2 * n + 2)
bit[x] += y, x += lowbit(x);
}
inline int query(int x) {
x++;
int sum = 0;
while (x)
sum += bit[x], x -= lowbit(x);
return sum;
}
signed main() {
Pow[0] = 1;
for (int i = 1; i < N; i++)
Pow[i] = Pow[i - 1] * 2 % Mod;
scanf("%lld", &n);
for (int i = 1, l, r; i <= n; i++) {
scanf("%lld %lld", &l, &r), l++;
a[++m] = make_pair(0, l - 1), a[++m] = make_pair(r + 1, 2 * n + 1);
}
sort(a + 1, a + m + 1);
for (int i = 1; i <= m; ) {
int j = i;
for ( ; j <= m; j++)
if (a[i].first == a[j].first);
else
break;
(ans += Pow[i - query(a[i].first - 1) - 1] * (Pow[j - i] - 1) % Mod) %= Mod;
j = i;
for ( ; j <= m; j++) {
if (a[i].first == a[j].first)
update(a[j].second, 1);
else
break;
}
i = j;
}
printf("%lld\n", (ans - Pow[n] + 1 + Mod) % Mod);
return 0;
}