UOJ507. 【JOISC2020】星座3(贪心)

UOJ507. 【JOISC2020】星座3(贪心)

网上都说这是道笛卡尔树的题,但我从 UOJ 上翻到了一个及其优秀的新写法 from LanrTable

我仔细看了看,觉得挺妙的想法,故书文以记之

将星星和高楼的坐标都按纵坐标从小到大排序,使用并查集维护从一个点向左向右在不穿墙的情况下最远能到达哪

然后维护一个树状数组表示每个位置放置所需的代价(先说做法再解释为什么,因为太神仙了!

看看当前位置在树状数组上的值 S,和这颗星星消除的代价 C 比较

  • S >= C 答案直接加上 C,因为在上面的点更容易和别的点冲突,而且代价更大,肯定不优,删掉
  • S < C 答案暂时加上 S,并将区间 ([L, R]) (表示能够到达区域)每个值加上 C - S

乍一看好像没什么道理,甚至随随便便就可以 Hack 一下?

但事实上它是正确的,请想象一下在某个局面时,强制一个点必选所获得的代价,可以发现位置靠下的星星与之冲突的都会有 (C_i-S_i) 的贡献,因为之前答案暂时加了 (S_i),那么在加上差就会变为 (C_i),而不冲突的则安然无恙

但我还是不会严格证明每一步都会是最优解QAQ,哪位神仙会可以教教我

以下为个人YY部分:

由构造方法可知,在以前的每一步均是最优解且没有修改过前面的状态,即保留下来的点删除不会更优,放入一个点时必将冲突点全部删除,这时冲突点的冲突点将全部释放(重新选中),而不会重新选中它们所冲突的更深层点,因为以前的保留点已经做到比选更深层点优

const int N = 200500;
int m, n;
struct DSU {
	int f[N];
	int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
}L, R;
ll d[N];
void add(int x, ll k) {
	for (; x <= n + 1; x += x & -x) d[x] += k;
}
ll sum(int x) {
	ll res = 0;
	for (; x; x -= x & -x) res += d[x];
	return res;
}
ll ans;
vector<pair<int, int> > st[N];
vector<int> h[N];
int main() {
	read(n); L.f[n+1] = R.f[n+1] = n+1;
	for (int i = 1;i <= n; i++) {
		L.f[i] = R.f[i] = i;
		read(m), h[m].emplace_back(i);
	}
	read(m);
	while (m--) {
		int x, y, z; 
		read(x), read(y), read(z);
		st[y].emplace_back(x, z);
	}
	for (register int i = 1;i <= n; i++) {
		for (pair<int, int> s: st[i]) {
			ll S = sum(s.fi);
			if (S >= s.se) ans += s.se;
			else ans += S, add(L.find(s.fi) + 1, s.se - S), add(R.find(s.fi), S - s.se);
		}
		for (int j: h[i]) L.f[j] = j - 1, R.f[j] = j + 1;
	}
	write(ans);
	return 0;
}

原文地址:https://www.cnblogs.com/Hs-black/p/13056705.html