ICode9

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

CF Edu124 F 题解

2022-06-09 21:10:18  阅读:135  来源: 互联网

标签:魔力 int 题解 nd CF Edu124 mp dt dec


Solution

记 \(X_k\) 为 \(\sum_{i=1}^kx_i\),即序列 \(x\) 的前缀和。

对于每座塔都有满魔力时,可以通过二分 \(C\) 来得到会推平哪个前缀。

对于每座塔在前一秒都没有魔力时,可以通过二分 \(R\) 来得到会推平哪个前缀。

对于每座塔在前 \(k\) 秒都没有魔力,且 \(r_ik\le c_i\) 时,可以也通过二分 \(R\) 来得到会推平哪个前缀。记 \(p=\lceil\frac{h}{k}\rceil\),对 \(p\) 二分即可。

记 \(f_i=\lceil\frac{c_i}{r_i}\rceil\)。对于经过时间 \(t\),一座塔 \(i\) 满魔力当且仅当 \(t\ge f_i\)。

如果不考虑 \(r_ik\le c_i\) 的限制:

可以尝试实现一个数据结构,能够支持查询的 \(t\ge f_i\) 塔的魔力总和与 \(t<f_i\) 的塔的恢复速率总和,就可以得到经过时间 \(t\) 后的魔力和。

考虑线段树

在下标 \(f_i\) 插入每个塔的 \(c_i\) 和 \(r_i\) 并维护 \(c\) 和 \(r\) 的区间和。查询时只需要查询前缀 \([1,t]\)。

问题是现在只支持维护魔力和,并不能算出要推平到哪个前缀。

考虑倍增

每次判断 \([1, v]\) 的塔的前缀魔力和是否大于 \(h\),大于就不跳,小于等于就跳。倍增要注意可能 \([1,1]\) 就大于 \(h\),不过这些都是基本的实现细节。

问题是线段树一下就插入了 \(n\) 个塔的信息,区分不了前 \(v\) 座塔魔力和 和 后 \(n-v\) 座塔的魔力和。

考虑主席树

按 \(1\sim n\) 的顺序依次在下标 \(f_i\) 插入 \(c_i,r_i\),对于查询区间 \([l,r]\),直接用版本 \(r\) 减去版本 \(l-1\)。注意到我们维护的信息是 \(c\) 和 \(r\) 的区间和,具有可减性,可以直接维护。

通过主席树和倍增,我们可以做到在 \(\mathcal O(\log^2 n)\) 的时间复杂度内查询推平哪个前缀。

仍有一个问题,实现上述做法后,我们能回答的问题都有一个限制:每座塔需要都在同一秒没有魔力。

显然实际上很难出现这样的情况。

不过刚才反复出现了推平二字……

考虑珂朵莉树

我们用珂朵莉树维护若干个区间 \([l,r,t]\),表示区间 \([l,r]\) 的每座塔都在 \(t\) 时刻被榨干。对于在 \(t\) 时刻没有完全榨干的塔,给它打个标记,分配一个 \([i,i,-t]\) 的线段(显然正常的 \(t\) 不会是负数,标记不会混淆)。

不过珂朵莉树的最大问题在于时间复杂度。

发现每次操作,我们会抹平一段前缀,并新建两个线段 \([1, p, t]\) 和 \([p,p,-t]\),总线段数是 \(\mathcal O(q)\) 的。而每个线段最多被插入一次,删除一次,对时间复杂度的总贡献是 \(\mathcal O(1)\) 的,珂朵莉树的总时间复杂度是 \(\mathcal O(q\log q)\) 的(用 mapset 实现自带 \(\log\))。而先前使用的主席树也可以支持查询的任意一个线段 \([l,r]\) 内的塔。

总时间复杂度 \(\mathcal O(q(\log^2 n+\log q))\)。

Code

// Problem: F. Tower Defense
// From: Codeforces - Educational Codeforces Round 124 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1651/problem/F
// Time: 2022-03-15 16:54
// Author: lingfunny

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int mxn = 2e5+10, mxd = 1e7;

int n, c[mxn], r[mxn], tt, root[mxn], lst[mxn], lg[mxn];
LL ans;
struct node {
	LL sc, sr; inline node operator + (const node& rhs) const { return {sc + rhs.sc, sr + rhs.sr}; }
} nd[mxd];
int lc[mxd], rc[mxd], tot;
#define mid ((L+R)>>1)
inline void psup(int o) { nd[o] = nd[lc[o]] + nd[rc[o]]; }
void insert(int& o, int rt, int p, node x, int L = 1, int R = mxn) {
	if(!o) o = ++tot; if(L == R) return nd[o] = nd[rt] + x, void();
	if(p <= mid) insert(lc[o], lc[rt], p, x, L, mid), rc[o] = rc[rt];
	else insert(rc[o], rc[rt], p, x, mid+1, R), lc[o] = lc[rt];
	psup(o);
}
node query(int r, int l, int p, int L = 1, int R = mxn) {
	// 计算版本r-l的小于等于 p 的 sc 和大于 p 的sr
	if(R <= p) return {nd[r].sc-nd[l].sc, 0}; if(L > p) return {0, nd[r].sr-nd[l].sr};
	return query(lc[r], lc[l], p, L, mid) + query(rc[r], rc[l], p, mid+1, R);
}
map <int, int> mp;
inline void cut(int p) {
	if(mp.find(p) == mp.end()) mp[p] = prev(mp.lower_bound(p))->second;
}
inline LL get(int l, int r, int dt) {
	node s = query(root[r], root[l-1], dt);
	return s.sc + (s.sr*dt);
}
// node[P] 时间为 P 的时候满魔力的塔的信息

signed main() {
	scanf("%d", &n); lg[0] = -1;
	for(int i = 1; i <= n; ++i) scanf("%d%d", c+i, r+i), lg[i] = lg[i>>1] + 1;
	for(int i = 1; i <= n; ++i) insert(root[i], root[i-1], min((c[i]+r[i]-1)/r[i], mxn), {c[i], r[i]});
	mp[n+1] = 114514; mp[1] = -1919810;
	scanf("%d", &tt);
	while(tt--) {
		int t, L, R, dt, pos; LL h, dec; scanf("%d%lld", &t, &h);
		++t;	// 存在 t = 0, 建议先 +1 以保证特殊标记可以区分开
		auto it = mp.begin();
		while(it->first != n+1) {
			if(it -> second < 0 && it -> second != -1919810) {
				// 这个点删了,但没完全删
				dt = t + it->second, pos = it -> first;
				dec = min((LL)c[pos], lst[pos] + (LL)r[pos] * dt);
				if(h >= dec) { h -= dec; it = mp.erase(it); }
				else { lst[pos] = dec - h; it -> second = -t; h = 0; break; }
			} else {
				L = it->first, R = next(it)->first - 1, dt = t - it->second;
				dec = get(L, R, dt);
				if(h >= dec) { h -= dec; it = mp.erase(it); }
				else {
					for(int d = lg[R-L+1], v; v = L + (1<<d), ~d; --d) if(v <= R) {
						// 判断 [L, v] 的和是否比 h 大,大了不跳,小了就跳
						dec = get(L, v, dt);
						if(h >= dec) h -= dec, L = v + 1;
					}
					if((dec = get(L, L, dt)) <= h) h -= dec, ++L;
					// 此时 get(L, L, dt) > h
					dec = min((LL)c[L], (LL)r[L] * dt);
					dec -= h; lst[L] = dec; h = 0; cut(L), cut(L+1);
					mp.erase(it); mp[L] = -t; break;
				}
			}
		}
		if(mp.find(1) == mp.end()) mp[1] = t; ans += h;
	}
	printf("%lld\n", ans);
	return 0;
}

标签:魔力,int,题解,nd,CF,Edu124,mp,dt,dec
来源: https://www.cnblogs.com/lingfunny/p/16361097.html

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

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

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

ICode9版权所有