离烦来了(25.3)
2025/3/17 *1
略
2025/3/18 *2
xyk 你上来读一下题......
10 min 后:我来确定一下要报 APIO 的名单,这个叉歪kei是谁啊?
P4823
定义 \(f_i\) 表示跑出去 \(i\) 个人最大还能凑出多高。
然后比较显然的排序不等式,是简单的叠罗汉模型,也是经典的。
跟有道 abc 的 f 很类似,就是 CSP 之前给 ycz 和 hsr 看的那道,由于只是确定了顺序而不知道要选哪些所以要 dp。
转移就类似背包:
点击查看代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0), cin.tie(nullptr), cout.tie(nullptr)
#define rep(i, j, k) for(int i = j; i <= k; ++i)
#define pre(i, j, k) for(int i = j; i >= k; --i)
using namespace std;
using LL = long long;
const int N = 1e6 + 5;
const int M = 31;
LL n, m, s, a[N], w[N], v[N], f[N], g[N], ret;
struct node {
int a, b;
bool operator < (const node &A) const {return a + b < A.a + A.b;}
} t[N];
void init() {
}
/*
f[i] 表示跑出去 i 个人最大还能凑出多少高度
先排序不等式搞一搞然后直接转移,其中排序不等式是经典的叠罗汉类型
*/
void sol() {
cin >> n;
memset(f, 0xcf, sizeof f), f[0] = 0;
rep(i, 1, n) cin >> t[i].a >> t[i].b, s += t[i].a;
sort(t + 1, t + 1 + n);
cin >> m, f[0] = s;
rep(i, 1, n) {
pre(j, i, 0)
if(f[j - 1] + t[i].b >= m) f[j] = max(f[j], f[j - 1] - t[i].a);
}
pre(i, n, 0) if(f[i] >= 0) cout << i, exit(0);
}
signed main() {
#ifdef Xdik
#else
// freopen("")
#endif
FASTIO;
int _; _ = 1;
while(_--) sol();
return 0;
}
P5365
看到这个 \(n\) 的表达式是 \(\max(5,(\log_2^n)^4)\),其实 \(n<100\)。
并且注意到值域不是很大可以对值域做背包,比较简单。
点击查看代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0), cin.tie(nullptr), cout.tie(nullptr)
#define rep(i, j, k) for(int i = j; i <= k; ++i)
#define pre(i, j, k) for(int i = j; i >= k; --i)
using namespace std;
using LL = long long;
const int N = 1e6 + 5;
const int M = 31;
LL n, m, s, a[N], v[N], f[N], g[N], ret;
void init() {
}
void sol() {
cin >> n >> m, f[0] = 1;
rep(i, 1, n) cin >> a[i];
rep(i, 1, n) cin >> v[i], s += a[i] * v[i];
rep(i, 1, n) {
pre(j, s, 0) {
for(int k = 1; k <= a[i] && k * v[i] <= j; ++k) f[j] = max(f[j], f[j - k * v[i]] * k);
}
}
while(f[ret] < m) ++ret;
cout << ret;
}
signed main() {
#ifdef Xdik
#else
// freopen("")
#endif
FASTIO;
int _; _ = 1;
while(_--) sol();
return 0;
}
P2224
奇怪的题目第一次见,不会做,看题解,得知定义状态 \(f_{i,j}\) 表示前 \(i\) 个物品机器 A 用了 \(j\) 时间机器 B 工作的最短时间。
这个诡异的状态相当于是将答案加入了状态,答案就是 \(\min\limits_{0\leq i \leq \operatorname{maxtime}}\max(f_{n,i},i)\)。
点击查看代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0), cin.tie(nullptr), cout.tie(nullptr)
#define rep(i, j, k) for(int i = j; i <= k; ++i)
#define pre(i, j, k) for(int i = j; i >= k; --i)
using namespace std;
using LL = long long;
const int N = 3e4 + 5;
const int M = 31;
LL n, m, s, a[N], w[N], v[N], f[2][N], g[N], ret, inf = 1e14;
struct node {
int a, b, c;
bool operator < (const node &A) const {return a + b < A.a + A.b;}
} t[N];
void init() {
}
/*
f[i][j] 表示前 i 个物品机器 A 用了 j 时间机器 B 所需的最小时间,也即把其中一个答案加入状态
*/
void sol() {
cin >> n; ret = inf;
rep(i, 1, n) cin >> t[i].a >> t[i].b >> t[i].c;
memset(f, 0x3f, sizeof f), f[0][0] = 0;
rep(i, 1, n) {
rep(j, 0, N - 5) {
f[i & 1][j] = inf;
if(t[i].a && j - t[i].a >= 0) f[i & 1][j] = min(f[i & 1][j], f[i - 1 & 1][j - t[i].a]);
if(t[i].b && j >= 0) f[i & 1][j] = min(f[i & 1][j], f[i - 1 & 1][j] + t[i].b);
if(t[i].c && j - t[i].c >= 0) f[i & 1][j] = min(f[i & 1][j], f[i - 1 & 1][j - t[i].c] + t[i].c);
}
}
rep(i, 0, N - 5) ret = min(ret, max(1LL * i, f[n & 1][i]));
cout << ret;
}
signed main() {
#ifdef Xdik
#else
// freopen("")
#endif
FASTIO;
int _; _ = 1;
while(_--) sol();
return 0;
}
2025/3/20 *3
P6879
很少见到这种题,上次见到好像是 TQ 的 dp 单元检测的奶牛题,重新看了一遍,这类题目的通性是一个傻逼可以往两个方向走然后干一些事情(贡献提前计算类区间 dp)。
常见的状态是定义 \(f_{l,r,0/1}\) 表示从 \(l\to r\) 最后停留在 \(l/r\) 对什么什么的最大最小值。
转移比较简单,考虑从 \([l+1,r]\) 和 \([l,r-1]\) 这两个区间转移过来,然后算从这些区间走过来的 \(w\) 转移,模板题是关路灯但是离烦没有拉。
点击查看代码
```plaintext
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0), cin.tie(nullptr), cout.tie(nullptr)
#define rep(i, j, k) for(int i = j; i <= k; ++i)
#define pre(i, j, k) for(int i = j; i >= k; --i)
using namespace std;
using LL = long long;
const int N = 4e2 + 5;
const int M = 31;
LL n, m, L, s, f[N][N][N][2], g[N], ret;
struct node {
LL x, t;
bool operator < (const node &a) const {
return t < a.t;
}
} a[N];
void init() {
}
/*
f[l][r][x][0/1] 表示 [l,r] 收集了 x 顺/逆时针走所需的最小时间
*/
void sol() {
cin >> n >> L, m = n * 2 + 1;
rep(i, 1, n) cin >> a[i].x;
rep(i, 1, n) cin >> a[i].t, a[i + n + 1].x = a[i].x + L, a[i + n + 1].t = a[i].t;
// sort(a + 1, a + 1 + n);
a[n + 1].x = L;
memset(f, 0x3f, sizeof f), f[n + 1][n + 1][0][0] = f[n + 1][n + 1][0][1] = 0;
// cerr << "debug";
rep(len, 2, n + 1) {
rep(l, 1, m - len + 1) {
int r = l + len - 1, x1, x2;
x1 = a[l + 1].x - a[l].x, x2 = a[r].x - a[l].x;
rep(p, 0, len) {
f[l][r][p][0] = min(f[l + 1][r][p][0] + x1, f[l + 1][r][p][1] + x2);
if(p) {
if(f[l + 1][r][p - 1][0] + x1 <= a[l].t) f[l][r][p][0] = min(f[l][r][p][0], f[l + 1][r][p - 1][0] + x1);
if(f[l + 1][r][p - 1][1] + x2 <= a[l].t) f[l][r][p][0] = min(f[l][r][p][0], f[l + 1][r][p - 1][1] + x2);
}
}
x1 = a[r].x - a[r - 1].x, x2 = a[r].x - a[l].x;
rep(p, 0, len) {
f[l][r][p][1] = min(f[l][r - 1][p][1] + x1, f[l][r - 1][p][0] + x2);
if(p) {
if(f[l][r - 1][p - 1][1] + x1 <= a[r].t) f[l][r][p][1] = min(f[l][r][p][1], f[l][r - 1][p - 1][1] + x1);
if(f[l][r - 1][p - 1][0] + x2 <= a[r].t) f[l][r][p][1] = min(f[l][r][p][1], f[l][r - 1][p - 1][0] + x2);
}
}
}
}
pre(i, n, 0) {
rep(len, 2, n + 1) {
rep(l, 1, m - len + 1) {
int r = l + len - 1;
if(min(f[l][r][i][0], f[l][r][i][1]) < 1e14) cout << i, exit(0);
}
}
}
}
signed main() {
#ifdef Xdik
#else
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
#endif
FASTIO;
int _;
_ = 1;
while(_--) sol();
return 0;
}
P2466
跟上面那道题一样的。
点击查看代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0), cin.tie(nullptr), cout.tie(nullptr)
#define rep(i, j, k) for(int i = j; i <= k; ++i)
#define pre(i, j, k) for(int i = j; i >= k; --i)
using namespace std;
using LL = long long;
const int N = 1e3 + 5;
const int M = 31;
LL n, m, L, s, f[N][N][2], g[N], ret, suf[N], pre[N];
struct node {
LL x, y, v;
bool operator < (const node &a) const { return x < a.x; }
} a[N];
void init() {
}
/*
f[l][r][0/1] 表示 [l,r] 左/右行动分数最大值
*/
void sol() {
cin >> n >> s; memset(f, 0xcf, sizeof f);
rep(i, 1, n) cin >> a[i].x, a[i].x -= s;
rep(i, 1, n) cin >> a[i].y;
rep(i, 1, n) cin >> a[i].v;
a[++n] = {0, 0, 0};
sort(a + 1, a + 1 + n);
rep(i, 1, n) if(a[i].x == 0) f[i][i][0] = f[i][i][1] = 0;
rep(i, 1, n) pre[i] = pre[i - 1] + a[i].v;
pre(i, n, 1) suf[i] = suf[i + 1] + a[i].v;
rep(len, 2, n) {
rep(l, 1, n - len + 1) {
LL r = l + len - 1, x1, x2, pp;
x1 = a[l + 1].x - a[l].x, x2 = a[r].x - a[l].x, pp = pre[l] + suf[r + 1];
f[l][r][0] = max(f[l + 1][r][0] - x1 * pp, f[l + 1][r][1] - x2 * pp) + a[l].y;
x1 = a[r].x - a[r - 1].x, x2 = a[r].x - a[l].x, pp = pre[l - 1] + suf[r];
f[l][r][1] = max(f[l][r - 1][1] - x1 * pp, f[l][r - 1][0] - x2 * pp) + a[r].y;
}
}
cout << fixed << setprecision(3) << 1.0 * max(f[1][n][0], f[1][n][1]) / 1000;
}
signed main() {
#ifdef Xdik
#else
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
#endif
FASTIO;
int _;
_ = 1;
while(_--) sol();
return 0;
}
ABC219H
场切了,弱化版可以烧到负数的做法是简单关路灯问题,这个题要避免烧到负数就再加一维限制 \(k\) 表示区间外面还有 \(k\) 支还没熄灭的的蜡烛,转移也很简单。
2025/3/24
一类区间压缩类的区间 dp,典题是 P4302,mjl 初一的时候拉过跟这个一模一样的,但是那个题要输出方案,当时我不会,超的我同桌的,他那个写的贼长,不知道怎么想出来的做法,现在重新看了一遍发现很 easy 啊。
P2470
可以嵌套循环,然后又有个什么 M,那就定义 \(f_{l,r,0/1}\) 表示区间 \([l,r]\) 有没有 M 的情况下长度压缩的最小值。
要跟 P4302 一样枚举一个区间左右两半是否相同,如果可以相同则有一个转移
\(mid\) 是区间中点,意义是将这两段一样的用一个 B 压缩。
2025/3/25
神秘人讲背包。
P4241
CF183D
P9669
2025/3/27
P4241
2025/3/31
P4657
很神奇的题,题目本质是求一条路径上选择至多 \(m\) 个点使得这些点周围的点权和最大。
朴素做法是定义 \(f_{x,i}\) 表示当前以 \(x\) 为根选了 \(i\) 个点答案最大值,转移很简单。
复杂度是 \(O(N^2 M)\) 的。
优化是直接定义 \(f_{x,i,0/1}\) 表示当前以 \(x\) 为根选了 \(i\) 个点向上/下走过来答案最大值,转移式子也很显然,注意要正反各跑一遍,还要算上向上/向下已经选了 \(m\) 个点的答案。
点击查看代码
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0), cin.tie(nullptr), cout.tie(nullptr)
#define rep(i, j, k) for (int i = j; i <= k; ++i)
#define pre(i, j, k) for (int i = j; i >= k; --i)
#define PII pair<int, int>
#define fi first
#define se second
#define pb push_back
using LL = long long;
using namespace std;
const int N = 1e5 + 5;
const int M = 105;
const int mod = 1e9 + 7;
#define cmax(a, b) (a = a > b ? a : b)
#define cmin(a, b) (a = a < b ? a : b)
LL n, m, a[N], w[N], u, v, ret, f[N][M][2], g[N][M][2];
vector<int> e[N];
//void dfs1(int x, int fa) {
// LL s = 0;
// rep(i, 1, m) f[x][i] = f[x][i] = 0;
// for(auto y : e[x]) if(y != fa) s += a[y];
// for(auto y : e[x]) {
// if(y == fa) continue;
// dfs1(y, x);
// rep (i, 1, m) {
// f[x][i] = max({f[x][i], f[y][i], f[y][i - 1] + s});
// f[x][i] = max(f[x][i], f[y][i]);
// }
// }
//}
/*
f[x][i][0/1] 表示以 x 为根用了 i 个磁铁某个点向上/下走到 x 的最大值
*/
void dfs2(int x, int fa) {
LL s = 0;
int s1 = 0, s2 = 0;
rep(i, 1, m) f[x][i][0] = w[x], f[x][i][1] = w[x] - a[fa];
for(auto y : e[x]) {
if(y == fa) continue;
dfs2(y, x);
rep(i, 1, m - 1) cmax(ret, f[x][i][0] + f[y][m - i][1]);
rep(i, 1, m) {
cmax(f[x][i][0], max(f[y][i][0], f[y][i - 1][0] + w[x] - a[y]));
cmax(f[x][i][1], max(f[y][i][1], f[y][i - 1][1] + w[x] - a[fa]));
}
}
rep(i, 1, m) f[x][i][0] = w[x], f[x][i][1] = w[x] - a[fa];
reverse(e[x].begin(), e[x].end());
for(auto y : e[x]) {
if(y == fa) continue;
rep(i, 1, m - 1) cmax(ret, f[x][i][0] + f[y][m - i][1]);
rep(i, 1, m) {
cmax(f[x][i][0], max(f[y][i][0], f[y][i - 1][0] + w[x] - a[y]));
cmax(f[x][i][1], max(f[y][i][1], f[y][i - 1][1] + w[x] - a[fa]));
}
}
cmax(ret, max(f[x][m][0], f[x][m][1]));
}
signed main() {
FASTIO;
cin >> n >> m;
rep(i, 1, n) cin >> a[i];
rep(i, 1, n - 1) cin >> u >> v, e[u].pb(v), e[v].pb(u), w[v] += a[u], w[u] += a[v];
dfs2(1, 0);
cout << ret;
return 0;
}

浙公网安备 33010602011771号