JOISC选做模拟赛总结(推性质dp,hall定理(特殊二分图最大匹配))
时间安排
三个半个小时写完 \(T1\),后面 \(1\) 个小时脑子炸了写了 \(5pts\) 却保龄。
总得分:100 + 0 + 0
rk1
反思:
- \(T1\) 没有那么麻烦,考试的时候应该再想想有没有更简单做法的。不要把简单问题困难话!!
- \(T2\) 也并不困难,败在了 \(T1\) 做时间太长。但是\(T2\) 思路一直都偏了。
- \(T3\) 是特殊二分图最大匹配,用 \(hall\) 定理,只能说原来没学过吧。
题解
「JOISC 2024 Day1」滑雪 2
分析:
推性质题。首先可以得到以下性质:
性质一: 最初的每种高度一定至少有一个点不被抬高。
证明:如果都被抬高相当于都没被抬高。
性质二: 一定存在一种最优方案,满足每种高度 \(c_i\) 最小的点不被抬高。
证明:由性质 \(1\) 可得每层至少一个点不被抬高,那么 \(c_i\) 最小的点不被抬高一定是最优的。
性质三:被抬高的点不可能花费 \(c_i\) 新建连接措施。
证明:被抬高的点一定不是它所在层 \(c_i\) 最小的,如果它新建连接措施,那么可以让它那一层 \(c_i\) 最小的点新建连接措施代替它,肯定更优。
其实性质 \(3\) 也能反过来更好的解释性质 \(1,2\):由于被抬高的点不会新建连接措施,因此即便它们到某一层后比这一层原来 \(c_i\) 最小的点的 \(c_i\) 还小,我们依然可以保留原来 \(c_i\) 最小的点,这样是不会影响答案最优性的,这解释了性质 \(2\)。那么连带着被提高上来的点看这一层的点,此时一定是不可能全提高的,因为全提高肯定不优,而我们说了可以保留原来当前层 \(c_i\) 最小的点,因此解释了性质 \(1\):每种最初高度的一定至少有一个点不被抬高。
然后可以 \(dp\):设 \(dp_{i, j, k}\) 表示考虑完了高度小于等于 \(i\) 的所有点,当前还能用的链接为 \(j\) 个,有 \(k\) 个点还需要拔高。
设 \(minc_{i}\) 表示 \(1 \sim i\) 高度 \(c\) 的最小值, \(cnt_{i}\) 表示原来高度为 \(i\) 的点的数量。
转移是枚举在第 \(i + 1\) 层连接了 \(x(x \leq j)\) 个点,以及新增了 \(y\) 个链接装置用来链接:
由于用了几个链接就会多出几个,因此第二维为 \(j + y\)。
发现每层肯定是把原来能用的 \(j\) 个都用完,因为用一个会补一个,不会减少。因此 \(x\) 的枚举是没有必要的。此时复杂度 \(O(n^3H)\)
然后发现可以将转移分开:
第一步:
相当于先不考虑 \(y\),只用完前面所有能用的,然后跟 \(i + 1\) 层合并。
第二步:
由于 \(y\) 的转移是一个类似完全背包的东西,从小到大枚举 \(j\),可以分成一步一步转移。
这样复杂度变成了 \(O(Hn^2)\)。
性质四:有用的高度只有 \(O(n)\) 个。
证明:显然只有将所有点铺开后占据的高度有用。
因此我们将有用的高度离散化高度,然后第一步转移中 \(k \times K\) 变成 \((H_{i + 1} - H_{i}) \times k \times K\) 即可。复杂度 \(O(n^3)\)。
需要注意 \((H_{i + 1} - H_{i}) \times k \times K\) 可能爆 \(long \ long\),但是注意到答案不会超过 \(9 \times 10^{13}\),将 \((H_{i + 1} - H_{i}) \times K\) 与 \(9 \times 10^{13}\) 取 \(min\) 即可。
CODE:
// 找性质,然后 dp
#include<bits/stdc++.h>
using namespace std;
const int N = 310;
typedef long long LL;
int n, tot, cnt[N];
LL K, H[N], minc[N], dp[N][N][N]; // dp[i][j][k] 表示考虑过了前 i 层,当前有 j 个空闲可以用,有 k 个要升高高度
const LL INF = 9e13;
struct node {
LL h, c;
}d[N];
bool cmp(node x, node y) {return x.h < y.h;}
int main() {
memset(minc, 0x3f, sizeof minc);
memset(dp, 0x3f, sizeof dp);
scanf("%d%lld", &n, &K);
for(int i = 1; i <= n; i ++ ) {
scanf("%lld%lld", &d[i].h, &d[i].c);
}
sort(d + 1, d + n + 1, cmp);
H[0] = -1;
for(int i = 1; i <= n; i ++ ) H[i] = max(H[i - 1] + 1, d[i].h);
for(int i = 1; i <= n; i ++ ) {
d[i].h = lower_bound(H + 1, H + n + 1, d[i].h) - (H);
cnt[d[i].h] ++; minc[d[i].h] = min(minc[d[i].h], d[i].c);
}
for(int i = 1; i <= n; i ++ ) minc[i] = min(minc[i], minc[i - 1]);
dp[1][1][cnt[1] - 1] = 0; int sum = 0;
for(int i = 1; i < n; i ++ ) { // 刷表
sum += cnt[i];
for(int j = 0; j <= n; j ++ ) {
for(int k = 0; k <= sum; k ++ ) {
int sz = max(k + cnt[i + 1] - j, 0);
dp[i + 1][j][sz] = min(dp[i + 1][j][sz], dp[i][j][k] + min(INF, (H[i + 1] - H[i]) * K) * k);
}
}
for(int j = 0; j < n; j ++ ) {
for(int k = 1; k <= sum + cnt[i + 1]; k ++ ) {
dp[i + 1][j + 1][k - 1] = min(dp[i + 1][j + 1][k - 1], dp[i + 1][j][k] + minc[i]);
}
}
}
LL res = INF;
for(int i = 0; i <= n; i ++ ) res = min(res, dp[n][i][0]);
cout << res << endl;
return 0;
}
「JOISC 2022 Day3」蚂蚁与方糖
感觉是比较套路的题?
题面
分析:
首先将方糖看做红点, 蚂蚁看做蓝点。不难发现这是一个特殊二分图求最大匹配的问题,考虑 \(Hall\) 定理。
\(Hall\) 定理:
对于一张二分图 \(G\),设左部点为 \(V_L\),右部点为 \(V_R\),\(N(v)\) 为 \(v\) 的邻域点集。
那么这张二分图的最大匹配为 \(|V_L| - \max\limits_{S \subseteq V_L}(|S| - \bigcup \limits_{v\in S}N(v))\)。其中 \(S\) 可以为空。
对于本题而言,一个在 \(x\) 位置上的红点的邻域就是 \([x - L, x + L]\) 范围内的所有蓝点。
设 \(V_R\) 表示红点点集, \(V_B\) 表示蓝点点集。
\(|V_R|\) 是很容易维护的,现在只要能快速求出 \(\max\limits_{S \subseteq V_R}(|S| - \bigcup \limits_{v\in S}N(v))\) 即可。不难发现选择的红点会形成若干段,并且相邻两段的距离 \(> 2L\)。因为如果 \(\leq 2L\),那么把两段合并成一段邻域蓝点数量不变,红点数量增加,答案会变得更优。
来把每一段的贡献表示出来:
对于选择的一段 \([l, r]\),贡献就是 \(\sum\limits_{i = l}^{r}R_i - \sum\limits_{i = l-L}^{r + L}B_i\)。其中 \(R_i\) 和 \(B_i\) 分别表示 \(i\) 位置上红点和蓝点数量。
将贡献拆成前缀和相减的形式:
\(\sum\limits_{i = l}^{r}R_i - \sum\limits_{i = l-L}^{r + L}B_i\)
\(=SR_{r} - SR_{l - 1} - SB_{r + L} + SB_{l - L - 1}\)
\(=(SR_{r} - SB_{r + L}) + (SB_{l - L - 1} - SR_{l - 1})\)
设 \(f_{r} = SR_{r} - SB_{r + L}\), \(g_{l} = SB_{l - L - 1} - SR_{l - 1}\)。
那么一段 \([l, r]\) 的贡献就等于 \(g_{l} + f_{r}\)。
相当于我们要在数组上交替选 \(g_i\), \(f_i\),满足开头是 \(g\),结尾是 \(f\),要求选出的数字之和最大。显然可以 \(ddp\),设 \(dp_{p, 0/1, 0/1}\) 表示 \(p\) 节点中选了若干个,最左边的是 \(g/f\),最右边是 \(g/f\) 的最大值,合并是容易的。
现在考虑带修改:
第一种,加入 \(R_{x}\):
发现会让 \([x, +\infty]\) 的 \(f_{i}\) 加 \(R_x\),\([x + 1, +\infty]\) 的 \(g_i\) 减 \(R_x\)。但是区间加是不好做的,因为我们不知道选了多少个 \(f,g\),也不知道对单个 \(f,g\) 区间加后会不会导致转移(也就是选择策略)发生变化。
但是可以将修改变成 :
- \([x + 1, +\infty]\) 的 \(f\) 加 \(R_x\),\([x + 1, +\infty]\) 的 \(g\) 减 \(R_x\)、
- 将 \([x, x]\) 的 \(f\) 加 \(R_x\)。
由于每种状态下 \(f\) 和 \(g\) 的选择数量最多差 \(1\),并且这个数量差是由状态\(0/1,0/1\) 定下来的,因此第一步修改不会改变选择策略,相当于一个区间 \(dp\) 值加减操作。
第二步是单点改,直接递归下去修改即可。
第二种,加入 \(B_{x}\):
会让 \([x - L, +\infty]\) 的 \(f\) 减 \(B_{x}\),\([x + L + 1, +\infty]\) 的 \(g\) 加 \(B_{x}\)。
还是按照上述方法拆成 \([x + L + 1, +\infty]\) 的 \(f\) 减 \(B_{x}\) 和 \(g\) 加 \(B_{x}\),同样是区间 \(dp\) 值加减。但是发现还剩下 \([x - L, x + L]\) 的 \(f\) 减 \(B_{x}\),这好像还是没法做??注意到这个区间长度小于等于 \(2L\),意味着这一个区间的最优策略中 \(f\) 和 \(g\) 都是最多选 \(1\) 个,并且也会在状态中体现,因此也相当于一个区间 \(dp\) 值加减。
然后是红点区间端点一定 \(Q\) 次询问加入的位置,因此把所有有用位置离散化,然后跑 \(ddp\)。
时间复杂度 \(O(n \log n)\)。
CODE:
// 求特殊二分图的最大匹配:hall定理
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
typedef long long LL;
const LL INF = 5e14;
int q, L;
int op[N], x[N];
int node[N * 2], tot;
LL a[N];
struct SegmentTree {
int l, r;
LL tag[2][2], f[2][2]; // f -> 1, g -> 0
#define l(x) t[x].l
#define r(x) t[x].r
#define tag(x) t[x].tag
#define f(x) t[x].f
}t[N * 2 * 4];
void build(int p, int l, int r) {
l(p) = l; r(p) = r;
if(l == r) {
f(p)[1][0] = -INF; // 这种状态是不合法的
return ;
}
int mid = (l + r >> 1);
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
}
void update(int p) {
for(int i = 0; i <= 1; i ++ ) // 跨过两段的
for(int j = 0; j <= 1; j ++ )
f(p)[i][j] = max(f(p << 1)[i][0] + f(p << 1 | 1)[1][j], f(p << 1)[i][1] + f(p << 1 | 1)[0][j]);
for(int i = 0; i <= 1; i ++ )
for(int j = 0; j <= 1; j ++ )
f(p)[i][j] = max({f(p)[i][j], f(p << 1)[i][j], f(p << 1 | 1)[i][j]});
}
void Add(int p, int a, int b, LL c) {
f(p)[a][b] += c; tag(p)[a][b] += c;
}
void spread(int p) {
for(int i = 0; i <= 1; i ++ )
for(int j = 0; j <= 1; j ++ )
if(tag(p)[i][j]) {
Add(p << 1, i, j, tag(p)[i][j]);
Add(p << 1 | 1, i, j, tag(p)[i][j]);
tag(p)[i][j] = 0;
}
}
void change(int p, int l, int r, int a, int b, LL c) {
if(l > r) return ;
if(l <= l(p) && r >= r(p)) {
Add(p, a, b, c);
return ;
}
spread(p);
int mid = (l(p) + r(p) >> 1);
if(l <= mid) change(p << 1, l, r, a, b, c);
if(r > mid) change(p << 1 | 1, l, r, a, b, c);
update(p);
}
void ins(int p, int pos, LL c) {
if(l(p) == r(p)) {
f(p)[1][1] += c; f(p)[0][1] += c;
return ;
}
spread(p);
int mid = (l(p) + r(p) >> 1);
if(pos <= mid) ins(p << 1, pos, c);
else ins(p << 1 | 1, pos, c);
update(p);
}
int main() {
scanf("%d%d", &q, &L);
for(int i = 1; i <= q; i ++ ) {
scanf("%d%d%lld", &op[i], &x[i], &a[i]);
node[++ tot] = x[i];
}
sort(node + 1, node + tot + 1);
tot = unique(node + 1, node + tot + 1) - (node + 1);
build(1, 1, tot); LL all = 0;
for(int i = 1; i <= q; i ++ ) {
if(op[i] == 1) { // 加入蓝点
int p = lower_bound(node + 1, node + tot + 1, x[i] + L + 1) - (node);
for(int j = 0; j <= 1; j ++ ) for(int k = 0; k <= 1; k ++ ) change(1, p, tot, j, k, (j + k - 1) * (-a[i])); // [p1, tot] f - a, g + a
int ll = lower_bound(node + 1, node + tot + 1, x[i] - L) - (node); // [ll, p1 - 1] f - a 注意到长度 <= 2L
for(int j = 0; j <= 1; j ++ ) for(int k = 0; k <= 1; k ++ ) change(1, ll, p - 1, j, k, (j | k) * (-a[i])); // 有 1 就减 1 个 a[i]
}
else { // 加入红点
all += a[i];
int pp = lower_bound(node + 1, node + tot + 1, x[i]) - (node);
for(int j = 0; j <= 1; j ++ ) for(int k = 0; k <= 1; k ++ ) change(1, pp + 1, tot, j, k, (j + k - 1) * a[i]); // [pp, tot] f + a, g - a
ins(1, pp, a[i]);
}
LL res = max(0LL, f(1)[0][1]);
printf("%lld\n", all - res);
}
return 0;
}

浙公网安备 33010602011771号