JOISC选做模拟赛总结(推性质dp,hall定理(特殊二分图最大匹配))

时间安排

三个半个小时写完 T 1 T1 T1,后面 1 1 1 个小时脑子炸了写了 5 p t s 5pts 5pts 却保龄。
总得分:100 + 0 + 0
rk1
反思:

  1. T 1 T1 T1 没有那么麻烦,考试的时候应该再想想有没有更简单做法的。不要把简单问题困难话!!
  2. T 2 T2 T2 也并不困难,败在了 T 1 T1 T1 做时间太长。但是 T 2 T2 T2 思路一直都偏了。
  3. 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 1i 高度 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(xj) 个点,以及新增了 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+1xydpi,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+1jdpi,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,k1dpi+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+1Hi)×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+1Hi)×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+1Hi)×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)) VLSVLmax(SvSN(v))。其中 S S S 可以为空。

对于本题而言,一个在 x x x 位置上的红点的邻域就是 [ x − L , x + L ] [x - L, x + L] [xL,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)) SVRmax(SvSN(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=lrRii=lLr+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=lrRii=lLr+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} =SRrSRl1SBr+L+SBlL1
= ( 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}) =(SRrSBr+L)+(SBlL1SRl1)

f r = S R r − S B r + L f_{r} = SR_{r} - SB_{r + L} fr=SRrSBr+L g l = S B l − L − 1 − S R l − 1 g_{l} = SB_{l - L - 1} - SR_{l - 1} gl=SBlL1SRl1
那么一段 [ 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 区间加后会不会导致转移(也就是选择策略)发生变化。
但是可以将修改变成 :

  1. [ 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
  2. [ 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] [xL,+] 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] [xL,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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值