题解:P12033 [USACO25OPEN] Package Pickup P
题意
数轴上的一些整点上放了牛和草。你每秒可以选择一头牛,令其向左或向右移动一个单位长度。每当一头牛和一捆草位于同一个位置时,牛就会吃掉这捆草。求吃完所有草的最短时间。
牛和草的初始位置以特殊方式给出:给定一个常量
分析
先考虑 Subtask 1,即牛和草的总数不超过
考虑分析一头牛的行为。一头牛可以吃光它经过的所有位置上放的草,而它经过的所有位置是一段区间。并且,牛经过了整个区间,当且仅当它经过了区间的左右端点,因此固定区间时一头牛的代价很容易计算出来。
考虑分析所有牛的行为。通过调整法可以发现,可以认为任意两头牛的区间都是不交的。
考虑一个很暴力的转化:把数轴按照牛和草初始所在的位置分段,则最优解中每一段中所有边经过的次数相等,且均为
考虑判定一个
- 草两侧两条线段不同时为
0 ,不分别为1 和2 。 - 牛两侧两条线段不同时为
1 ,不同时为2 。 - 每个非
0 的段至少经过一头牛。
用这个充要条件容易写出 DP。设
转移是容易的。注意特判同一个位置有两头牛的情况,此时两头牛可以分别移动。
然后一般情况是容易的。观察到
实现
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
#define all(x) begin(x), end(x)
#define sz(x) int((x).size())
using ll = long long;
using pi = pair<int, int>;
using vi = vector<int>;
struct Mat {
ll a[5][5];
Mat operator*(const Mat &b) const {
Mat ans;
memset(ans.a, 0x3f, sizeof ans.a);
rep (i, 0, 4)
rep (j, 0, 4)
rep (k, 0, 4)
ans.a[i][k] = min(ans.a[i][k], a[i][j] + b.a[j][k]);
return ans;
}
static Mat Make(int b, ll d) {
Mat ans;
memset(ans.a, 0x3f, sizeof ans.a);
if (b == 0) {
fill(all(ans.a[2]), d);
fill(all(ans.a[3]), 2 * d);
memset(ans.a[4], 0, sizeof ans.a[4]);
} else if (b == 1) {
ans.a[2][1] = ans.a[2][2] = ans.a[2][4] = d;
ans.a[3][0] = ans.a[3][3] = ans.a[3][4] = 2 * d;
memset(ans.a[4], 0, sizeof ans.a[4]);
} else {
ans.a[0][0] = ans.a[0][4] = d;
ans.a[1][1] = ans.a[1][4] = 2 * d;
ans.a[2][2] = d;
ans.a[3][3] = 2 * d;
ans.a[4][2] = ans.a[4][3] = 0;
}
return ans;
}
};
struct I {
ll c, g;
ll bg, ed;
int b;
Mat mul;
I operator+(const I &x) const {
if (c == 0 && g == 0 && x.c == 0 && x.g == 0)
return {.ed = ed + x.ed};
if (c == 0 && g == 0) {
I ans = x;
ans.bg += ed;
return ans;
}
if (x.c == 0 && x.g == 0) {
I ans = *this;
ans.ed += x.ed;
return ans;
}
return {c + x.c, g + x.g, bg,
x.ed, x.b, x.mul * Mat::Make(b, ed + x.bg) * mul};
}
I operator*(ll n) const {
assert(n >= 0);
I ans = {}, add = *this;
for (; n > 0; n >>= 1) {
if ((n & 1) == 1)
ans = ans + add;
add = add + add;
}
return ans;
}
};
vector<ll> raw;
struct SegT {
static constexpr int N = (1 << 16) + 10;
int n;
I t[2 * N];
void Build() {
n = sz(raw) == 2 ? 1 : 2 << (31 ^ __builtin_clz(sz(raw) - 2));
rep (i, 0, n - 1) {
t[i + n] = {.ed = i <= sz(raw) - 2 ? raw[i + 1] - raw[i] : 0};
memset(t[i + n].mul.a, 0x3f, sizeof t[i + n].mul.a);
rep (j, 0, 4)
t[i + n].mul.a[j][j] = 0;
}
for (int i = n - 1; i >= 1; --i)
t[i] = t[2 * i] + t[2 * i + 1];
}
void Upd(int x, ll c, ll g) {
x += n;
t[x].c += c, t[x].g += g;
if (t[x].c > 1) {
t[x].b = 0;
} else if (t[x].c > 0) {
t[x].b = 1;
} else if (t[x].g > 0) {
t[x].b = 2;
}
while (x > 1) {
x >>= 1;
t[x] = t[2 * x] + t[2 * x + 1];
}
}
I Qry() {
return t[1];
}
} sgt;
ll m;
int n, p;
struct E {
ll t, c, g;
bool operator<(E x) const {
return t < x.t;
}
};
vector<E> e;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin.exceptions(cin.failbit);
cin >> m >> n >> p;
raw = {0, m};
rep (i, 1, n) {
ll l, r;
cin >> l >> r;
raw.push_back(l % m);
e.push_back({l, 1, 0}), e.push_back({r + m, -1, 0});
}
rep (i, 1, p) {
ll l, r;
cin >> l >> r;
raw.push_back(l % m);
e.push_back({l, 0, 1}), e.push_back({r + m, 0, -1});
}
sort(all(raw));
raw.erase(unique(all(raw)), end(raw));
sort(all(e));
sgt.Build();
I ans = {};
ll lst = -1;
for (auto [t, c, g] : e) {
if (lst != -1)
ans = ans + sgt.Qry() * (t / m - lst);
lst = t / m;
sgt.Upd(lower_bound(all(raw), t % m) - begin(raw), c, g);
}
ll f[5];
rep (i, 0, 4)
f[i] = ans.mul.a[i][4];
if (ans.b <= 1)
cout << *min_element(all(f)) << '\n';
else
cout << min(f[2], f[3]) << '\n';
}