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;
}