[CTSC2015]日程管理[题解]
日程管理
\(Problem\)
幽香是幻想乡中一个非常有地位的人。她日理万机,事务繁多,反倒自己已经快管理不过来了。于是他决定开发一个日程管理软件来帮助自己管理任务。
对于每个任务 \(i\) 有一个对应的截止日期 \(t_i\) 以及收益 \(p_i\),表示若幽香能在不晚于第 \(t_i\) 天完成这个任务,便可以得到 \(p_i\) 的收益。幽香办事的能力非常强,任何任务都可以用恰好一天的时间做完。但由于任务实在太多了,有时候并不能完成所有任务,于是幽香会想知道这个情况下,完成任务可以给她带来的最大的累积收益是多少。
由于幻想乡的人们十分善变,任务总是不断发生着变化。幽香希望这个管理软件还能够支持插入一个任务,和删除一个任务的操作。
具体的说,幽香希望支持以下 \(2\) 个操作:
-
\(ADD\) \(t\) \(p\):表示新添一个截止日期为 \(t\),收益为 \(p\) 的任务。
-
\(DEL\) \(t\) \(p\):表示删除一个截止日期为 \(t\),收益为 \(p\) 的任务。如果有多个这样的任务,只删除一个。数据保证这样的任务一定存在。
在每次操作执行完毕后,你都需要输出能够完成的任务的最大收益和。
幽香一共有 \(T\) 天需要安排,从第 \(1\) 天到第 \(T\) 天。你能帮助他写出这个高效率的软件吗?
数据范围
\(T\leq 3\times 10 ^ 5,Q\leq 3\times 10 ^ 5\)
\(Sol\)
分别考虑添加和删除:
-
添加:建立一棵线段树,叶子结点储存每个时刻完成任务的价值。每次查询 \(1\) 至 \(t\) 范围内元素的最小值,如果小于 \(p\),则将其替换,这是一个很自然的想法。考虑如下情况,若范围 \(1\) 至 \(t\) 外有一个更小的值,且可以和范围内的某个值合法替换,那我们显然应该选择更小的值。为此,我们可以再建一棵线段树,储存每个时刻完成任务的截止时间,每次查询 \(1\) 至 \(t\) 内所完成任务的截止时间的最大值,设这个值为 \(far\),再查询 \(1\) 至 \(far\) 范围内完成任务的截止时间的最大值,若这个值比 \(far\) 大,则交换线段树上的两个节点,并重复这个过程,直到找到最大的边界。再用这个最大的边界进行最小值查询,此时,才是我们可以进行替换的最小值。而添加还有一个问题,有可能我们无法进行替换,或者有任务被替换了下来,这些任务我们该如何储存。我们先暂且将它们按照截止时间存入若干个 \(multiset\) 中即可。
-
删除:如果删除的树不在线段树上,则十分简单,直接在 \(multiset\) 中将其剔除即可。若在线段树上,我们需要从之前储存在 \(multiset\) 中的任务选出一个将其替换掉。我们建立第三棵线段树,叶子节点储存每个截止时刻对应的 \(multiset\) 中的最大元素。设被删除的任务在时刻 \(t\) 完成,我们只需在这棵线段树上查询区间 \(t\) 到 \(T\) 的最大值,然后再替换上去,同时更新 \(multiset\) 和线段树即可,这同样是比较自然的想法。但问题在于被删除的任务目前被完成的时间不一定是它最早可以被完成的时间,所以我们需要确定这个最早的时间,扩大查询范围。这一点可以通过二分实现,\(l = 1, r = t - 1\),二分出一个 \(mid\) 在第二棵线段树上查询范围 \(1\) 到 \(mid\) 范围内完成任务截止时间的最大值,倘若大于等于 \(t\),则代表这个任务可以被替换到更靠前的位置,重复这个过程,知道当前任务不再能前移为止,之后的过程即如上。
这就是这道题的整体思路,可能不是十分难想到,但却比较难实现,实现的过程不赘述,需要注意的是完全一致的任务可能会出现多个,写代码时可能要注意这个问题。
\(code\)
#include <bits/stdc++.h>
#define st first
#define nd second
#define mk make_pair
#define int long long
#define pii pair<int, pair<int, int> >
using namespace std;
const int N = 3e5 + 10, INF = 1e15;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
inline int read()
{
register int x = 0, f = 0;
register char c = getchar();
while (!isdigit(c)) { if (c == '-') f = 1;c = getchar(); }
while (isdigit(c)) { x = (x << 3) + (x << 1) + c - 48;c = getchar(); }
return f ? -x : x;
}
struct Tree{
int v, p, c;
bool operator < (const Tree &x) const{ return v < x.v; }
}tr[N << 2]; //该线段树记录 1~T 时间范围内时刻 p 完成的任务的价值
pii lv[N << 2]; //该线段树记录 1~T 时间范围内结束时间为 nd 且未被完成任务的最大价值
pii tim[N << 2]; //该线段树记录 1~T 时间范围内时刻 nd 完成的任务的截止时间
int T, Q, ans, id[N];
#define lson k << 1
#define rson k << 1 | 1
#define mid ((l + r) >> 1)
inline void build(int k, int l, int r)
{
if(l == r) { tr[k].p = lv[k].nd.st = tim[k].nd.st = l; id[l] = k; return; }
build(lson, l, mid), build(rson, mid + 1, r);
tr[k] = min(tr[lson], tr[rson]);
}
inline void change(int k, int l, int r, int x, int v, int t, int c)
{
if(l == r && l == x) { tr[k].v = v, tim[k].st = t; tr[k].c = c, tim[k].nd.nd = c; return; }
if(x <= mid) change(lson, l, mid, x, v, t, c);
else change(rson, mid + 1, r, x, v, t, c);
tr[k] = min(tr[lson], tr[rson]), tim[k] = max(tim[lson], tim[rson]);
}
inline void update(int k, int l, int r, int x, int v, int c)
{
if(l == r && l == x) { lv[k].st = v, lv[k].nd.nd = c; return; }
if(x <= mid) update(lson, l, mid, x, v, c);
else update(rson, mid + 1, r, x, v, c);
lv[k] = max(lv[lson], lv[rson]);
}
inline Tree query(int k, int l, int r, int x, int y) //查询 [x,y] 时间范围内完成任务价值的最小值
{
if(x > y) return (Tree){INF, 0, 0};
if(l >= x && r <= y) return tr[k];
if(y <= mid) return query(lson, l, mid, x, y);
else if(x > mid) return query(rson, mid + 1, r, x, y);
else return min(query(lson, l, mid, x, mid), query(rson, mid + 1, r, mid + 1, y));
}
inline pii ask(int k, int l, int r, int x, int y) //查询 [x,y] 时间范围内完成的任务的截止时间的最大值
{
if(x > y) return mk(-INF, mk(0, 0));
if(l >= x && r <= y) return tim[k];
if(y <= mid) return ask(lson, l, mid, x, y);
else if(x > mid) return ask(rson, mid + 1, r, x, y);
else return max(ask(lson, l, mid, x, mid), ask(rson, mid + 1, r, mid + 1, y));
}
inline pii Get_mx(int k, int l, int r, int x, int y) //查询 [x,y] 时间范围内未被完成的任务的最大价值
{
if(x > y) return mk(-INF, mk(0, 0));
if(l >= x && r <= y) return lv[k];
if(y <= mid) return Get_mx(lson, l, mid, x, y);
else if(x > mid) return Get_mx(rson, mid + 1, r, x, y);
else return max(Get_mx(lson, l, mid, x, mid), Get_mx(rson, mid + 1, r, mid + 1, y));
}
multiset<pair<int, int> > s[N];
map<pii, int> mp;
map<pair<int, int>, int> cnt;
inline pii sub(int t, int p, int c) { return mk(t, mk(p, c)); }
inline void exchange(int x, int y) //交换在时刻 x 和时刻 y 完成的任务
{
int recv = tr[id[x]].v, rect = tim[id[x]].st, recc = tr[id[x]].c;
mp[sub(tim[id[y]].st, tr[id[y]].v, tr[id[y]].c)] = x, change(1, 1, T, x, tr[id[y]].v, tim[id[y]].st, tr[id[y]].c); //将 y 换到 x
mp[sub(rect, recv, recc)] = y, change(1, 1, T, y, recv, rect, recc); //将 x 换到 y
}
inline void add(int t, int p, int c)
{
if(!s[t].size()) update(1, 1, T, t, p, c), s[t].insert(mk(p, c));
else{
auto mx = s[t].end(); mx--;
if((*mx).st < p) update(1, 1, T, t, p, c);
s[t].insert(mk(p, c));
}
}
inline void del(int t, int p, int c)
{
s[t].erase(s[t].lower_bound(mk(p, c)));
if(!s[t].size()) update(1, 1, T, t, 0, 0);
else{
auto mx = s[t].end(); mx--;
update(1, 1, T, t, (*mx).st, (*mx).nd);
}
}
inline void replace(int x, int t, int p, int c) //代替时刻 x 的任务
{
int nt = tim[id[x]].st, np = tr[id[x]].v, nc = tr[id[x]].c;
mp[sub(nt, np, nc)] = 0, mp[sub(t, p, c)] = x, change(1, 1, T, x, p, t, c);
if(np) add(nt, np, nc); //若置换的不是空位置
}
signed main()
{
T = read(), Q = read(), build(1, 1, T);
while(Q--){
string opt;
for(register int i = 0; i <= 2; i++) { char ch = getchar(); opt.push_back(ch); }
int t = read(), p = read();
if(opt == "ADD"){ //添加操作
cnt[mk(t, p)]++;
while(1){
pii far = ask(1, 1, T, 1, t), New = ask(1, 1, T, 1, far.st);
if(New.st > far.st) exchange(New.nd.st, far.nd.st);
else break;
}
pii far = ask(1, 1, T, 1, t);
Tree res = query(1, 1, T, 1, far.st);
if(res.p > t) exchange(res.p, far.nd.st);
res = query(1, 1, T, 1, t);
if(res.v < p) ans += (p - res.v), replace(res.p, t, p, cnt[mk(t, p)]);
else add(t, p, cnt[mk(t, p)]);
}
else{ //删除操作
int c = cnt[mk(t, p)];
if(mp[sub(t, p, c)]){
ans -= p;
while(1){
int l = 1, r = mp[sub(t, p, c)] - 1, res = -1;
while(l <= r){
if(ask(1, 1, T, 1, mid).st >= mp[sub(t, p, c)]) res = mid, r = mid - 1;
else l = mid + 1;
}
if(res != -1) exchange(res, mp[sub(t, p, c)]);
else break;
}
pii res = Get_mx(1, 1, T, mp[sub(t, p, c)], T);
if(res.st){ //删除 mp[mk(t, p)],插入 res.nd res.st
mp[sub(res.nd.st, res.st, res.nd.nd)] = mp[sub(t, p, c)];
change(1, 1, T, mp[sub(t, p, c)], res.st, res.nd.st, res.nd.nd), mp[sub(t, p, c)] = 0;
del(res.nd.st, res.st, res.nd.nd), ans += res.st;
}
else change(1, 1, T, mp[sub(t, p, c)], 0, 0, 0), mp[sub(t, p, c)] = 0;
}
else del(t, p, c);
cnt[mk(t, p)]--;
}
printf("%lld\n", ans);
}
return 0;
}