时间安排
三个半个小时写完
T
1
T1
T1,后面
1
1
1 个小时脑子炸了写了
5
p
t
s
5pts
5pts 却保龄。
总得分:100 + 0 + 0
rk1
反思:
- T 1 T1 T1 没有那么麻烦,考试的时候应该再想想有没有更简单做法的。不要把简单问题困难话!!
- T 2 T2 T2 也并不困难,败在了 T 1 T1 T1 做时间太长。但是 T 2 T2 T2 思路一直都偏了。
- T 3 T3 T3 是特殊二分图最大匹配,用 h a l l hall hall 定理,只能说原来没学过吧。
题解
「JOISC 2024 Day1」滑雪 2
分析:
推性质题。首先可以得到以下性质:
性质一: 最初的每种高度一定至少有一个点不被抬高。
证明:如果都被抬高相当于都没被抬高。
性质二: 一定存在一种最优方案,满足每种高度
c
i
c_i
ci 最小的点不被抬高。
证明:由性质
1
1
1 可得每层至少一个点不被抬高,那么
c
i
c_i
ci 最小的点不被抬高一定是最优的。
性质三:被抬高的点不可能花费
c
i
c_i
ci 新建连接措施。
证明:被抬高的点一定不是它所在层
c
i
c_i
ci 最小的,如果它新建连接措施,那么可以让它那一层
c
i
c_i
ci 最小的点新建连接措施代替它,肯定更优。
其实性质
3
3
3 也能反过来更好的解释性质
1
,
2
1,2
1,2:由于被抬高的点不会新建连接措施,因此即便它们到某一层后比这一层原来
c
i
c_i
ci 最小的点的
c
i
c_i
ci 还小,我们依然可以保留原来
c
i
c_i
ci 最小的点,这样是不会影响答案最优性的,这解释了性质
2
2
2。那么连带着被提高上来的点看这一层的点,此时一定是不可能全提高的,因为全提高肯定不优,而我们说了可以保留原来当前层
c
i
c_i
ci 最小的点,因此解释了性质
1
1
1:每种最初高度的一定至少有一个点不被抬高。
然后可以 d p dp dp:设 d p i , j , k dp_{i, j, k} dpi,j,k 表示考虑完了高度小于等于 i i i 的所有点,当前还能用的链接为 j j j 个,有 k k k 个点还需要拔高。
设 m i n c i minc_{i} minci 表示 1 ∼ i 1 \sim i 1∼i 高度 c c c 的最小值, c n t i cnt_{i} cnti 表示原来高度为 i i i 的点的数量。
转移是枚举在第
i
+
1
i + 1
i+1 层连接了
x
(
x
≤
j
)
x(x \leq j)
x(x≤j) 个点,以及新增了
y
y
y 个链接装置用来链接:
d
p
i
+
1
,
j
+
y
,
k
+
c
n
t
i
+
1
−
x
−
y
←
d
p
i
,
j
,
k
+
k
×
K
+
y
×
m
i
n
c
i
dp_{i + 1, j + y, k + cnt_{i + 1} - x - y} \gets dp_{i, j, k} + k \times K + y \times minc_{i}
dpi+1,j+y,k+cnti+1−x−y←dpi,j,k+k×K+y×minci
由于用了几个链接就会多出几个,因此第二维为
j
+
y
j + y
j+y。
发现每层肯定是把原来能用的
j
j
j 个都用完,因为用一个会补一个,不会减少。因此
x
x
x 的枚举是没有必要的。此时复杂度
O
(
n
3
H
)
O(n^3H)
O(n3H)
然后发现可以将转移分开:
第一步:
d
p
i
+
1
,
j
,
k
+
c
n
t
i
+
1
−
j
←
d
p
i
,
j
,
k
+
k
×
K
dp_{i + 1, j, k + cnt_{i + 1} - j} \gets dp_{i, j, k} + k \times K
dpi+1,j,k+cnti+1−j←dpi,j,k+k×K
相当于先不考虑 y y y,只用完前面所有能用的,然后跟 i + 1 i + 1 i+1 层合并。
第二步:
d
p
i
+
1
,
j
+
1
,
k
′
−
1
←
d
p
i
+
1
,
j
,
k
′
+
m
i
n
c
i
dp_{i + 1, j+1, k' - 1} \gets dp_{i + 1, j, k'} + minc_{i}
dpi+1,j+1,k′−1←dpi+1,j,k′+minci
由于 y y y 的转移是一个类似完全背包的东西,从小到大枚举 j j j,可以分成一步一步转移。
这样复杂度变成了 O ( H n 2 ) O(Hn^2) O(Hn2)。
性质四:有用的高度只有
O
(
n
)
O(n)
O(n) 个。
证明:显然只有将所有点铺开后占据的高度有用。
因此我们将有用的高度离散化高度,然后第一步转移中 k × K k \times K k×K 变成 ( H i + 1 − H i ) × k × K (H_{i + 1} - H_{i}) \times k \times K (Hi+1−Hi)×k×K 即可。复杂度 O ( n 3 ) O(n^3) O(n3)。
需要注意 ( H i + 1 − H i ) × k × K (H_{i + 1} - H_{i}) \times k \times K (Hi+1−Hi)×k×K 可能爆 l o n g l o n g long \ long long long,但是注意到答案不会超过 9 × 1 0 13 9 \times 10^{13} 9×1013,将 ( H i + 1 − H i ) × K (H_{i + 1} - H_{i}) \times K (Hi+1−Hi)×K 与 9 × 1 0 13 9 \times 10^{13} 9×1013 取 m i n min 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」蚂蚁与方糖
感觉是比较套路的题?
题面
分析:
首先将方糖看做红点, 蚂蚁看做蓝点。不难发现这是一个特殊二分图求最大匹配的问题,考虑
H
a
l
l
Hall
Hall 定理。
H
a
l
l
Hall
Hall 定理:
对于一张二分图
G
G
G,设左部点为
V
L
V_L
VL,右部点为
V
R
V_R
VR,
N
(
v
)
N(v)
N(v) 为
v
v
v 的邻域点集。
那么这张二分图的最大匹配为
∣
V
L
∣
−
max
S
⊆
V
L
(
∣
S
∣
−
⋃
v
∈
S
N
(
v
)
)
|V_L| - \max\limits_{S \subseteq V_L}(|S| - \bigcup \limits_{v\in S}N(v))
∣VL∣−S⊆VLmax(∣S∣−v∈S⋃N(v))。其中
S
S
S 可以为空。
对于本题而言,一个在
x
x
x 位置上的红点的邻域就是
[
x
−
L
,
x
+
L
]
[x - L, x + L]
[x−L,x+L] 范围内的所有蓝点。
设
V
R
V_R
VR 表示红点点集,
V
B
V_B
VB 表示蓝点点集。
∣
V
R
∣
|V_R|
∣VR∣ 是很容易维护的,现在只要能快速求出
max
S
⊆
V
R
(
∣
S
∣
−
⋃
v
∈
S
N
(
v
)
)
\max\limits_{S \subseteq V_R}(|S| - \bigcup \limits_{v\in S}N(v))
S⊆VRmax(∣S∣−v∈S⋃N(v)) 即可。不难发现选择的红点会形成若干段,并且相邻两段的距离
>
2
L
> 2L
>2L。因为如果
≤
2
L
\leq 2L
≤2L,那么把两段合并成一段邻域蓝点数量不变,红点数量增加,答案会变得更优。
来把每一段的贡献表示出来:
对于选择的一段
[
l
,
r
]
[l, r]
[l,r],贡献就是
∑
i
=
l
r
R
i
−
∑
i
=
l
−
L
r
+
L
B
i
\sum\limits_{i = l}^{r}R_i - \sum\limits_{i = l-L}^{r + L}B_i
i=l∑rRi−i=l−L∑r+LBi。其中
R
i
R_i
Ri 和
B
i
B_i
Bi 分别表示
i
i
i 位置上红点和蓝点数量。
将贡献拆成前缀和相减的形式:
∑
i
=
l
r
R
i
−
∑
i
=
l
−
L
r
+
L
B
i
\sum\limits_{i = l}^{r}R_i - \sum\limits_{i = l-L}^{r + L}B_i
i=l∑rRi−i=l−L∑r+LBi
=
S
R
r
−
S
R
l
−
1
−
S
B
r
+
L
+
S
B
l
−
L
−
1
=SR_{r} - SR_{l - 1} - SB_{r + L} + SB_{l - L - 1}
=SRr−SRl−1−SBr+L+SBl−L−1
=
(
S
R
r
−
S
B
r
+
L
)
+
(
S
B
l
−
L
−
1
−
S
R
l
−
1
)
=(SR_{r} - SB_{r + L}) + (SB_{l - L - 1} - SR_{l - 1})
=(SRr−SBr+L)+(SBl−L−1−SRl−1)
设
f
r
=
S
R
r
−
S
B
r
+
L
f_{r} = SR_{r} - SB_{r + L}
fr=SRr−SBr+L,
g
l
=
S
B
l
−
L
−
1
−
S
R
l
−
1
g_{l} = SB_{l - L - 1} - SR_{l - 1}
gl=SBl−L−1−SRl−1。
那么一段
[
l
,
r
]
[l, r]
[l,r] 的贡献就等于
g
l
+
f
r
g_{l} + f_{r}
gl+fr。
相当于我们要在数组上交替选 g i g_i gi, f i f_i fi,满足开头是 g g g,结尾是 f f f,要求选出的数字之和最大。显然可以 d d p ddp ddp,设 d p p , 0 / 1 , 0 / 1 dp_{p, 0/1, 0/1} dpp,0/1,0/1 表示 p p p 节点中选了若干个,最左边的是 g / f g/f g/f,最右边是 g / f g/f g/f 的最大值,合并是容易的。
现在考虑带修改:
第一种,加入
R
x
R_{x}
Rx:
发现会让
[
x
,
+
∞
]
[x, +\infty]
[x,+∞] 的
f
i
f_{i}
fi 加
R
x
R_x
Rx,
[
x
+
1
,
+
∞
]
[x + 1, +\infty]
[x+1,+∞] 的
g
i
g_i
gi 减
R
x
R_x
Rx。但是区间加是不好做的,因为我们不知道选了多少个
f
,
g
f,g
f,g,也不知道对单个
f
,
g
f,g
f,g 区间加后会不会导致转移(也就是选择策略)发生变化。
但是可以将修改变成 :
- [ x + 1 , + ∞ ] [x + 1, +\infty] [x+1,+∞] 的 f f f 加 R x R_x Rx, [ x + 1 , + ∞ ] [x + 1, +\infty] [x+1,+∞] 的 g g g 减 R x R_x Rx、
- 将 [ x , x ] [x, x] [x,x] 的 f f f 加 R x R_x Rx。
由于每种状态下
f
f
f 和
g
g
g 的选择数量最多差
1
1
1,并且这个数量差是由状态
0
/
1
,
0
/
1
0/1,0/1
0/1,0/1 定下来的,因此第一步修改不会改变选择策略,相当于一个区间
d
p
dp
dp 值加减操作。
第二步是单点改,直接递归下去修改即可。
第二种,加入
B
x
B_{x}
Bx:
会让
[
x
−
L
,
+
∞
]
[x - L, +\infty]
[x−L,+∞] 的
f
f
f 减
B
x
B_{x}
Bx,
[
x
+
L
+
1
,
+
∞
]
[x + L + 1, +\infty]
[x+L+1,+∞] 的
g
g
g 加
B
x
B_{x}
Bx。
还是按照上述方法拆成
[
x
+
L
+
1
,
+
∞
]
[x + L + 1, +\infty]
[x+L+1,+∞] 的
f
f
f 减
B
x
B_{x}
Bx 和
g
g
g 加
B
x
B_{x}
Bx,同样是区间
d
p
dp
dp 值加减。但是发现还剩下
[
x
−
L
,
x
+
L
]
[x - L, x + L]
[x−L,x+L] 的
f
f
f 减
B
x
B_{x}
Bx,这好像还是没法做??注意到这个区间长度小于等于
2
L
2L
2L,意味着这一个区间的最优策略中
f
f
f 和
g
g
g 都是最多选
1
1
1 个,并且也会在状态中体现,因此也相当于一个区间
d
p
dp
dp 值加减。
然后是红点区间端点一定 Q Q Q 次询问加入的位置,因此把所有有用位置离散化,然后跑 d d p ddp ddp。
时间复杂度 O ( n log n ) O(n \log n) O(nlogn)。
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;
}