## 夺宝奇猫
#数学 #贪心
首先我们需要构建一条 $1\sim n$ 和一条 $n\sim 1$ 的路径,这两个路径涵盖了所有的点组并且不重复。很显然后面 $n\sim 1$ 也等同于 $1\sim n$,因此我们需要构建两条 $1\sim n$ 的路径。
考虑相邻编号点 $i$ 和 $i+1$ 之间的路径方式,只有两种 ${} (i_0,(i+1)_{0})$ $(i_1,(i+1)_1) {}$ 和 ${} (i_0,(i+1)_1)$ $(i_1,(i+1)_0)$。因此我们只需要选择距离和最小的那一种,累加起来再额外加上第 $n$ 组点的贡献就行了。
时间复杂度 $\mathcal O(n)$。
```cpp
#include <fstream>
using namespace std;
using LL = long long;
const LL kMaxN = 1e5 + 5;
ifstream cin("treacat.in");
ofstream cout("treacat.out");
LL x[kMaxN][2], y[kMaxN][2], n, m, ans;
LL dis(LL x1, LL y1, LL x2, LL y2) {
return abs(x1 - x2) + abs(y1 - y2);
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for (LL i = 1; i <= n; i++) {
for (LL j = 0; j <= 1; j++) {
cin >> x[i][j] >> y[i][j];
}
}
for (LL i = 1; i < n; i++) {
ans += min(dis(x[i][0], y[i][0], x[i + 1][0], y[i + 1][0]) + dis(x[i][1], y[i][1], x[i + 1][1], y[i + 1][1]),
dis(x[i][0], y[i][0], x[i + 1][1], y[i + 1][1]) + dis(x[i][1], y[i][1], x[i + 1][0], y[i + 1][0]));
}
cout << ans + dis(x[n][0], y[n][0], x[n][1], y[n][1]) << '\n';
return 0;
}
```
## 爬山
#最短路 #优先队列
海拔上升会减少体力,海拔下降会增加体力,但是任意时刻体力不能为负,而 $h_1$ 不能改变(且改变了也不优),因此实际上可以走的海拔区间为 $(-\infty,h_1+k]$。对于路径上所有在区间之外的海拔,需要额外增加 $(h-h_1-k)^2$ 的代价。因此直接跑 dijkstra 额外增加贡献就行了。
时间复杂度 $\mathcal O(m\log_2 m)$。
```cpp
#include <fstream>
#include <vector>
#include <queue>
using namespace std;
using LL = long long;
const LL kMaxN = 2e5 + 5;
ifstream cin("climb.in");
ofstream cout("climb.out");
struct Node {
LL to, v;
Node(LL to, LL v):
to(to), v(v) { }
friend bool operator<(const Node &a, const Node &b) {
return a.v > b.v;
}
};
LL a[kMaxN], f[kMaxN], d[kMaxN], n, m, k;
vector<Node> e[kMaxN];
priority_queue<Node> q;
LL sqr(LL x) {
return x * x;
}
void bfs() {
fill(d + 2, d + n + 1, 1e18);
for (q.emplace(1, 0); q.size(); q.pop()) {
auto t = q.top();
if (f[t.to]) {
continue;
}
f[t.to] = 1;
for (auto i : e[t.to]) {
LL v = d[t.to] + i.v + sqr(max(0LL, a[i.to] - a[1] - k));
if (v < d[i.to]) {
d[i.to] = v;
q.emplace(i.to, d[i.to]);
}
}
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> k;
for (LL i = 1; i <= n; i++) {
cin >> a[i];
}
for (LL i = 1, u, v, w; i <= m; i++) {
cin >> u >> v >> w;
e[u].emplace_back(v, w);
e[v].emplace_back(u, w);
}
bfs();
cout << d[n] << '\n';
return 0;
}
```
## 排列
#序列dp #前缀和
首先很容易想到简单的动态规划,设 $dp_{i,j}$ 表示为对于长度为 $i$ 的已构建排列,最后一个元素是 $j$ 的方案数。但是选择下一个数的时候无法判断是否已经被选过,为了避免这种情况我们将 $j$ 改为在已选排列中的排名。
设 $dp_{i,j}$ 表示最后一个元素的排名为 $j$,那么转移和之前是一样的进行,也是非常的简单。增加前缀和优化把时间复杂度 $\mathcal O(n^3)$ 变成 $\mathcal O(n^2)$,并加上滚动数组优化防止炸空间。
```cpp
#include <fstream>
using namespace std;
using LL = long long;
const LL kMaxN = 1e4 + 5, kMod = 1e9 + 7;
ifstream cin("perm.in");
ofstream cout("perm.out");
LL dp[kMaxN], prv[kMaxN], sum[kMaxN], n, ans;
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n;
prv[1] = sum[1] = 1;
for (LL i = 2; i <= n; i++) {
for (LL j = 1; j <= i; j++) {
if (i % 2 == 0) {
dp[j] = (sum[i - 1] - sum[j - 1] + kMod) % kMod;
} else {
dp[j] = sum[j - 1];
}
}
for (LL j = 1; j <= i; j++) {
sum[j] = (sum[j - 1] + dp[j]) % kMod;
}
copy(dp + 1, dp + i + 1, prv);
}
for (LL i = 1; i <= n; i++) {
ans = (ans + dp[i]) % kMod;
}
cout << ans << '\n';
return 0;
}
```
## 夺宝奇鱼
#线段树 #排序 #贪心
首先由于限制对于所有怪兽都是启用的,因此可以枚举这个限制。枚举 $i$ 表示所有怪兽手上剩余的物品数量都小于 $i$,此时我们购买的物品数量就需要大于等于 $i$。从大到小枚举 $i$,这样每次都会把一些怪物手上的已有的物品删除。
现在我们让所有怪物都满足了 $i$ 的限制,但是自己手上可能还不到 $i$ 个物品。此时需要在怪物手上继续购买剩余的物品,肯定是优先购买便宜的,然而怪物手上的物品是越删越少的,因此可以使用权值线段树维护怪物手上的剩余物品,补充剩余的就在权值线段树上面二分就行了。
时间复杂度 $\mathcal O(m\log_2 m)$。注意需要进行离散化否则会 MLE。
```cpp
#include <fstream>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
using LL = long long;
const LL kMaxN = 1e6 + 5;
ifstream cin("treasure.in");
ofstream cout("treasure.out");
struct Node {
LL cnt, sum;
} tr[4 * kMaxN];
LL val[kMaxN], n, m, q, cnt, use, buy, ans = 1e18;
vector<LL> v[kMaxN], id[kMaxN], rm;
multiset<LL> s;
void modify(LL p, LL l, LL r, LL x, LL d) {
tr[p].cnt += d;
tr[p].sum += val[x] * d;
if (l == r) {
return;
}
LL m = (l + r) / 2;
if (x <= m) {
modify(2 * p, l, m, x, d);
} else {
modify(2 * p + 1, m + 1, r, x, d);
}
}
LL query(LL p, LL l, LL r, LL k) {
if (l == r) {
return val[l] * k;
}
LL m = (l + r) / 2, cnt = tr[2 * p].cnt;
if (k <= cnt) {
return query(2 * p, l, m, k);
}
return tr[2 * p].sum + query(2 * p + 1, m + 1, r, k - cnt);
}
LL getHash(LL x) {
return lower_bound(val + 1, val + q + 1, x) - val;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for (LL i = 1, a, c; i <= m; i++) {
cin >> a >> c;
v[c].push_back(a);
val[++q] = a;
}
sort(val + 1, val + q + 1);
q = unique(val + 1, val + q + 1) - val - 1;
for (LL i = 1; i <= n; i++) {
if (v[i].empty()) {
continue;
}
for (LL &j : v[i]) {
j = getHash(j);
modify(1, 1, q, j, 1);
}
sort(v[i].begin(), v[i].end(), greater<>());
id[v[i].size()].push_back(i);
}
// 所有怪物剩余物品数小于 i,购买数大于等于 i
for (LL i = m; i >= 1; i--) {
for (LL j : id[i]) {
rm.push_back(j);
}
for (LL j : rm) {
buy += val[v[j].back()];
modify(1, 1, q, v[j].back(), -1);
v[j].pop_back();
use++;
}
ans = min(ans, buy + query(1, 1, q, max(0LL, i - use)));
}
cout << ans << '\n';
return 0;
}
```
## 总结
总分:$100+100+100+100=400$。
第一、二、四题都做得很快,思路也不难想,就是第三题太糖了一个多小时才做出来。