ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JOISC 2019 day2 两道料理

2022-07-31 19:33:04  阅读:187  来源: 互联网

标签:rt return int sum day2 JOISC 2019 fi se


建模转化+维护差分序列

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
*/

标签:rt,return,int,sum,day2,JOISC,2019,fi,se
来源: https://www.cnblogs.com/wyb-sen/p/16537916.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有