JOISC 2019 day2 两道料理

建模转化+维护差分序列

Statement

厨师比太郎正在参加一个厨艺比赛。在这场比赛中参赛者要烹饪两道料理:IOl盖饭和JOI咖喱。

l0I盖饭的烹饪过程中需要N个步骤。第i(1≤i≤N)步的用时是 \(A_i\) 分钟,最初他只能进行第1步,想要进行第i(2≤i≤N)步的条件是已经完成了第i―1步。

JOI咖喱的烹饪过程中需要M个步骤。第j(1≤j≤M)步的用时是 \(B_i\)分钟,最初他只能进行第1步,想要进行第i(2≤j ≤ M)步的条件是已经完成了第j-1步。

做料理过程中需要专心致志,所以当他开始进行一个步骤时,就不能中断。当完成了一个步骤,他也可以选择进行另一道料理的下一个步骤。比赛开始后,在两道料理都完成之前,他不能停下来休息。

在这场比赛中,参赛者会按照接下来的方式得到艺术感的打分:

  • 如果他在比赛的前S(1≤i≤N)分钟内完成了IOI盖饭的第à个步骤,那么从中他会得到P;点的分数,分数有可能是负的。
  • 如果他在比赛的前T(1≤j≤M)分钟内完成了JOI咖喱的第j个步骤,那么从中他会得到Qj点的分数,分数有可能是负的。

请你帮助比太郎设计做料理过程,最大化他做料理的艺术感评分。

\(n,m \le 10^6\)

Solution

很有意思的一道题!

naive :显然,有用的时刻可以表示为 \((x, y)\) 表示做了前 \(x\)\(A\), 前 \(y\)\(B\),此时,每一个加分项也可以表示成 \((\le x, \le y)\) 之类的形式。这时候,wyb 突然脑洞,联想到 [ARC101D]Robots and Exits 的建模方式,即看作是从 \((0,0)\) 走到 \((n, m)\) ,每步向上向右,然后越过某一条线会有贡献。

对应到这里,就成了 \(x,y\) 轴上分别竖立这若干根和这根坐标轴垂直的线,点经过这条线就会有贡献。

然后感觉这个两种贡献很难受,不好搞,需要找个方式转化一下....(没有时间思考了/fn/fn/fn 思考时间给得太少)

yysy,想到这里,可以称之为高光时刻了 /hanx

正解:平凡地想到这个建模方式的思路应该是设 \(f(x, y)\) 表示做 \(x\)\(A\), 前 \(y\)\(B\) 的最大价值,然后通过对 DP 转移方程的观察看出是一个在二维平面上行走的问题,再把方程中的转移条件转化。

最终得到的模型:从 \((0,0)\) 开始往 \((n,m)\) 每步向上向右,平面上撒着若干个点,对于一个红点 \((i, y_i)\),如果在路径上或者路径上方,有 \(p_i\) 贡献;对于一个蓝点 \((x_i,i)\) ,如果在路径上或者路径下方,有 \(q_i\) 贡献。

注意路径上指的是被路径覆盖。

不好算,简单容斥,先给答案加上 \(\sum p\), 然后把 \(p_i\) 取负。此时需要算的是在路径下方的红点和在路径上/路径下方的蓝点,不统一。

考虑把红点 向左向上整体平移, 那么此时的在路径上/下方 就对应原来的在路径下方

此时,设 \(f(x, y)\) 表示走到 \(x,y\) 的最大贡献,设 \(sum(x,y)\) 表示 \((x,0\dots y)\) 的总贡献,有

\[f(x,y) = \max(f(x, y - 1), f(x - 1, y) + sum(x - 1, y)) \]

即我们认为拐角处才贡献,不会算重。注意这里 \(sum\) 不一定单调

竭诚观察这个式子, 思考 \(f\) 的值之间的关系。可以看做是先把 \(f(x - 1)\) 贺了一份过来,此时 \(f(x)\) 是单调的。然后在前缀的某些点上挂上一些标记表示对从 \(i\) 开始的所有值加上一个可正可负的数。

既然是单调的,我们可以以差分的视角来看待这个问题 。原 f(x - 1) 差分数组非负。

考虑当前如果加入的是一个正数,会把差分数组上这个位置增加
当前如若加入的是一个负数,对于差分数组当前数 \(d[i]\) 而言,如果 \(d[i] + x < 0\) 被干成了负数,因为对 \(f(x, y - 1)\) 取了 \(\max\),所以变成 \(0\);而 \(i + 1\) 位置相当于 \(i - 1\) 的增量是 \(d[i] + d[i + 1]\) ,所以是比较 \(d[i]+d[i+1]\)\(x\)

所以加入一个负数会使得后面连续的一段总和最多为 \(x\) 的数清零 (最后所有数都为 \(0\) 或有一个数只被减去了一部分)。这个过程可以直接用 set/线段树二分 维护。

更顺畅地得到这个维护差分做法的思路是先考虑暴力的做法
暴力做法需要支持的是区间加、所有数取前缀 max
假设这两种操作是交替的,考虑每次把一段区间 \([l,r]\) 整体抬高 \(h\),此时由于前缀 max, 需要二分出第一个位置和 \(a[r]\) 的差值大于 \(h\) ;把一段区间整体降低 \(h\) ,那需要二分出来的是那个位置不需要向前面取 \(\max\)
由此,发现我们非常关注差值的变化,所以我们考虑维护差分数组。
由此,引出了线段树上二分的思路。

一共写 + 调 接近 4h,最开始不懂这个怎么才能 set, 然后自己口胡了一个线段树上二分上去,觉得非常科学

然后狂暴乱肝,狂暴输出+画图手模,2h30min 左右总算是过了样例,交上去 49pts, WA

写了一个暴力对拍,总算是发现了问题 /hanx 写完回头看了一眼 set 的写法,发现其实很好懂而且好写/qd

bugs:

  • lj 编译器不支持 fread
  • 线段树上找到查询区间进一步往里走的时候,不要忘记 pushdown
  • query 区间和的时候,写法中存在 \(l, r\)\([L,R]\) 无交的情况,死循环
  • \((i,y_i) \to (i - 1, y_i + 1)\) 之后, 可能会大于 \(m\)
  • 对于 \(x_i = n\) 的情况,必须贡献 (卡死我了

Code

#include<bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
#define ls rt << 1
#define rs rt << 1 | 1
#define mid ((l + r) >> 1) 
using namespace std;
const int N = 1e6 + 5; 
const int inf = 1e9;

char buf[1 << 23], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1 ++)
int read() {
	int s = 0, w = 1; char ch = getchar();
	while(!isdigit(ch)) { if(ch == '-') w = -1; ch = getchar(); }
	while(isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
	return s * w;
} 

struct Tree {
	int sum, tg;
} t[N << 2];
int a[N], s[N], p[N];
int b[N], r[N], q[N];
vector<pii> vec[N];
int n, m, cnt, ans;

bool cmp(pii x, pii y) {
	return x.fi == y.fi ? x.se < y.se : x.fi > y.fi;
}

void pushup(int rt) {
	t[rt].sum = t[ls].sum + t[rs].sum;
}
void pushdown(int rt) {
	if(t[rt].tg == 1) {
		t[ls].sum = 0, t[ls].tg = 1;
		t[rs].sum = 0, t[rs].tg = 1;
		t[rt].tg = 0;
	}
}
void alter(int l, int r, int rt, int id, int v) {
	if(l == r) return t[rt].sum += v, void();
	pushdown(rt);
	if(id <= mid) alter(l, mid, ls, id, v);
	else alter(mid + 1, r, rs, id, v);
	pushup(rt);
}
void reset(int l, int r, int rt, int L, int R) {
	if(L > R || l > R || r < L) return ; 
	if(L <= l && r <= R) 
		return t[rt].sum = 0, t[rt].tg = 1, void();
	pushdown(rt);
	if(L <= mid) reset(l, mid, ls, L, R);
	if(mid < R) reset(mid + 1, r, rs, L, R);
	pushup(rt);
}
int query(int l, int r, int rt, int L, int R) {
	if(L > R || l > R || r < L) return 0;
	if(L <= l && r <= R) return t[rt].sum; pushdown(rt);
	if(R <= mid) return query(l, mid, ls, L, R);
	if(L > mid) return query(mid + 1, r, rs, L, R);
	return query(l, mid, ls, L, R) + query(mid + 1, r, rs, L, R);
}
int binary(int l, int r, int rt, int L, int R, int pre, int v) {
	if(L <= l && r <= R) {
		if(l == r) {
			if(pre + t[rt].sum >= v) 
				return l;
			return inf;
		}
		pushdown(rt); ////////// Segment Tree stikes me!
		if(t[ls].sum + pre >= v)
			return binary(l, mid, ls, L, R, pre, v);
		return binary(mid + 1, r, rs, L, R, pre + t[ls].sum, v);
	}
	pushdown(rt);
	int tmp = inf;
	if(L <= mid) tmp = min(tmp, binary(l, mid, ls, L, R, pre, v));
	if(mid < R) tmp = min(binary(mid + 1, r, rs, L, R, pre + query(l, mid, ls, L, R), v), tmp);
	return tmp; 
} 

signed main() {
//	freopen("data.in", "r", stdin);
	n = read(), m = read();
	for(int i = 1; i <= n; ++ i) 
		a[i] = read() + a[i - 1], s[i] = read() - a[i], p[i] = read();
	for(int i = 1; i <= m; ++ i) 
		b[i] = read() + b[i - 1], r[i] = read() - b[i], q[i] = read();
		
	for(int i = 1; i <= n; ++ i) if(s[i] >= 0) {
		int pos = upper_bound(b + 1, b + 1 + m, s[i]) - b;
		if(pos <= m) vec[i - 1].push_back(pii(- p[i], pos)); // the check is necessory, as it's illegal
		ans += p[i];
	}
	for(int i = 1; i <= m; ++ i) if(r[i] >= 0) 
		vec[upper_bound(a + 1, a + 1 + n, r[i]) - a - 1].push_back(pii(q[i], i));
		
	for(int i = 0; i < n; ++ i) {
		sort(vec[i].begin(), vec[i].end(), cmp); 
		for(auto v : vec[i]) 
			if(v.fi >= 0)
				alter(0, m, 1, v.se, v.fi);  // the upper border is m instead of m + 1 !!!
			else {
				int r = binary(0, m, 1, v.se, m, 0, -v.fi);
				if(r == inf) reset(0, m, 1, v.se, m);
				else alter(0, m, 1, r, v.fi + query(0, m, 1, v.se, r - 1)),
					reset(0, m, 1, v.se, r - 1);
					// 先 alter 再 reset 懂不懂啊 
			}
	} 
	
	ans += t[1].sum;
	for(auto v :vec[n]) ans += v.fi;
	printf("%lld\n", ans);
	return 0;
}
/*
4 3
2 1 1
3 8 1
2 13 1
1 13 1
3 6 1
2 11 1
2 15 1
*/
posted @ 2022-07-31 19:26  _Famiglistimo  阅读(308)  评论(4编辑  收藏  举报