CodeTON Round 5 (Div. 1 + Div. 2, Rated, Prizes!) E. Tenzing and Triangle

本文讨论了在给定斜率为k的直线上,通过构造三角形删除点以实现最小成本的过程,利用动态规划和区间查询技术求解

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem Description

在一个二维平面上有 nnn 个不同的点和一条 x+y=kx+y=kx+y=k 的直线,对于每个点 (xi,yi)(x_i,y_i)(xi,yi) 都满足 0≤xi,yi,xi+yi<k0 \le x_i,y_i,x_i+y_i < k0xi,yi,xi+yi<k

TenzingTenzingTenzing 想要删除所有点,他可以进行以下两种操作:

  1. 画三角形:选择两个非负整数 a,ba,ba,b ,满足 a+b<ka+b<ka+b<k ,则以 x=a,y=b和x+y=kx=a,y=b和x+y=kx=a,y=bx+y=k 构成的三角形内的点都会被删除,设三角形边长为 l,l,2ll,l,\sqrt{2}ll,l,2l ,那么将花费 l×Al \times Al×A
  2. 删除一个特定的点 iii ,代价是 cic_ici

算出最小花费。

Input

第一行输入三个整数 n,k,A(1≤n,k≤2×105,1≤A≤104)n,k,A(1 \le n,k \le 2 \times 10^5,1\le A \le 10^4)n,k,A(1n,k2×105,1A104) ,表示点的个数,斜线 x+y=kx+y=kx+y=k 相关系数和画三角形的基础花费的代价。

接下来 nnn 行每行输入三个整数 xi,yi,ci(0≤xi,yi,xi+yi<k,1≤ci≤104)x_i,y_i,c_i(0 \le x_i,y_i,x_i+y_i <k,1 \le c_i \le 10^4)xi,yi,ci(0xi,yi,xi+yi<k,1ci104) ,表示坐标和单个点删除的代价。

Output

输出删去所有点的最小花费。

Solution

对于两个重叠的三角形,我们会发现左图花费更大覆盖面积更小,右图花费更小覆盖面积更大,那么有个很显然的结论是三角形删点操作是相互分离、独立的,由此我们便考虑如何去 dpdpdp

![[Pasted image 20230825113257.png]]![[Pasted image 20230825113338.png]]

我们设 dpidp_idpi 表示为删除 y≤iy \le iyi 的所有点的最小花费。

![[Pasted image 20230825115630.png]]

那么我们就可以得到一个比较暴力的状态转移:

dpi=min⁡j∈[0,i−1](dpi−1+∑posy=icpos  ,  dpj+A(i−j)+∑pos∈V2cpos)dp_i = \min\limits_{j \in [0,i-1]}(dp_{i-1}+\sum\limits_{pos_y=i}c_{pos} \;,\;dp_j+A(i-j)+\sum\limits_{pos \in V_2}c_{pos})dpi=j[0,i1]min(dpi1+posy=icpos,dpj+A(ij)+posV2cpos)
对于 min⁡\minmin 的左部中的 ∑posy=icpos\sum\limits_{pos_y=i}c_{pos}posy=icpos ,只需在遍历到 iii 时对 posy=ipos_y=iposy=i 这一层的点权简单地求一遍和就行,整体复杂度是只有 O(n)O(n)O(n)

而对于右侧,这个是一个区间求最小值问题,那么此时我们便可以考虑是否可以改动公式来支持快速区间询问,以下给出一种改动方案:
dpj+A(i−j)+∑pos∈V2cpos→(dpj−A⋅j+∑pos∈V2cpos)+A⋅idp_j+A(i-j)+\sum\limits_{pos \in V_2}c_{pos} \to (dp_j-A·j+\sum\limits_{pos \in V_2}c_{pos})+A·idpj+A(ij)+posV2cpos(dpjAj+posV2cpos)+Ai
我们先只考虑 dpj−A⋅jdp_j-A·jdpjAj ,其实我们在每次求完 dpjdp_jdpj 时便能在线段树的 jjj 位置加上 dpj−A⋅jdp_j-A·jdpjAj ,那么在遍历到 iii 时,只需 querymin(0,i−1)query_{min}(0,i-1)querymin(0,i1) 即可,那么现在的问题就出现在了如何处理 ∑pos∈V2cpos\sum\limits_{pos \in V_2}c_{pos}posV2cpos

![[Pasted image 20230826014158.png]]

如上图所示,当我们的 jjjL1L_1L1 的范围内取时,点 ppp 都是属于 ∑pos∈V2cpos\sum\limits_{pos \in V_2}c_{pos}posV2cpos 的,那么此时我们就能很容易地想到,我们只需当遍历到 i==ypi==y_pi==yp 时在线段树上的 (xp,i](x_p,i](xp,i] 都加上 cpc_pcp ,而此处有两点值得注意:

  1. (xp,i](x_p,i](xp,i] 左端取开区间是由于当取到 j==xpj==x_pj==xp 时,点 ppp 此时是所做 V3V_3V3 的一部分,所以不应当加上 cpc_pcp
  2. 当我们算完第 iiidpidp_idpi 时,应当在线段树上的 iii 位置加上 dpi−A⋅i−∑posy=icposdp_i-A·i-\sum\limits_{pos_y=i}c_{pos}dpiAiposy=icpos ,这是由于 dpidp_idpi 表示的是删除 y≤iy \le iyi 的所有点的最小花费,∑posy=icpos\sum\limits_{pos_y=i}c_{pos}posy=icpos 已经包含在 dpidp_idpi 中,但是在线段树中又在 iii 位置上了,所以此处要减去以免重复计算。

最终时间复杂度为 O((n+k)logk)O((n+k)logk)O((n+k)logk)

Code

#include <bits/stdc++.h>
#define endl '\n'
#define lowbit(x) x &-x
using namespace std;
const int N = 2e5 + 10;
int n, k, A;
struct node {
	int l, r;
	int val, delta;
} tr[4 * N], *ls, *rs;
inline void pushdown(int u) {
	if (tr[u].delta) {
		ls = &tr[u << 1], rs = &tr[u << 1 | 1];
		ls->val += tr[u].delta, ls->delta += tr[u].delta;
		rs->val += tr[u].delta, rs->delta += tr[u].delta;
		tr[u].delta = 0;
	}
}
inline void pushup(int u) {
	tr[u].val = min(tr[u << 1].val, tr[u << 1 | 1].val);
}
void build(int u, int l, int r) {
	tr[u] = {l, r, 0};
	if (l == r) return;
	int mid = l + r >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
	pushup(u);
}
void modify(int u, int l, int r, int x) {
	if (tr[u].r < l || tr[u].l > r)return;
	if (l <= tr[u].l && tr[u].r <= r) {
		tr[u].delta += x;
		tr[u].val += x;
		return;
	}
	pushdown(u);
	modify(u << 1, l, r, x);
	modify(u << 1 | 1, l, r, x);
	pushup(u);
}
int query(int u, int l, int r) {
	if (tr[u].l > r || tr[u].r < l)return 2e9;
	if (l <= tr[u].l && tr[u].r <= r) return tr[u].val;
	pushdown(u);
	int L = query(u << 1, l, r);
	int R = query(u << 1 | 1, l, r);
	return min(L, R);
}
vector<pair<int, int>>cor[N];
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> k >> A;
	build(1, 0, k);
	for (int i = 1; i <= n; i++) {
		int x, y, c;
		cin >> x >> y >> c;
		cor[y].push_back({x, c});
	}
	vector<int>dp(k + 1);
	for (int i = 1; i <= k; i++) {
		int sum = 0;
		for (auto [x, c] : cor[k - i]) {
			sum += c;
			modify(1, x + 1, i, c);
		}
		dp[i] = min(dp[i - 1] + sum, query(1, 0, i - 1) + A * i);
		modify(1, i, i, dp[i] - A * i - sum);
	}
	cout << dp[k] << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值