Loading

【题解】[JOISC2020] 星座 3

近期做的最好的贪心题之一。

翻了一下官方题解貌似是转化为树上问题然后线段树维护,可能出题人想少了没有想到贪心写法。懂日语的小伙伴可以研究一下Solution

第一步不难想到对于所有的星星按 \(Y\) 从小到大排序,这样限制条件转化为选了一颗星星后,接下来不能选择一个区间内的星星。

那么对于当前的星星,如果前面没有星星能覆盖到它,那么肯定选他。

否则就有两种选择,第一种是选择当前星星,第二种是放弃当前星星。

我们知道已选的覆盖当前星星的所有星星 \(C\) 之和为 \(W\),讨论一下。

首先我们知道,如果选择当前星星而放弃前面星星,ban 掉的区间一定不会减小。

反证一下,如果减小那么一定存在放弃的星星不与当前星星构成一个星座。

所以如果 \(W\ge C_i\) 一定放弃当前星星,因为代价更小且 ban 掉的区间也更小。

否则如果放弃当前星星,一定会使得区间 \([l,r]\) 被 ban ,这里的 \([l,r]\) 是指左右能扩展的黑格子。

所以如果后面不存在 \([l,r]\) 之内的星星,那么一定放弃前面星星更优,因为后面的决策已经与当前无关了。

那么后面存在 \([l,r]\) 之内的星星呢,如果后面存在的这颗星星的 \(C\) 很大,那么放弃当前星星会更优,这相当于我们要反过来考虑前面的情况。

这是什么?反悔贪心,反悔贪心,反悔贪心!

如果后面 \([l,r]\) 之内的星星的 \(C\) ,加上 \(W\) 大于当前星星的 \(C\) ,这时候我们就进行反悔操作,放弃当前星星。

所以我们只用维护区间加,单点查询,和求出区间 \([l,r]\)

前者我们可以直接树状数组维护,后者我们借助并查集在线维护。

时间复杂度 \(\mathcal{O}((N+M)\log N)\)

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 200005
using namespace std;
typedef long long ll;
int l[N],r[N],n,m;ll c[N];
inline void add(int x,ll y){for(;x<=n;x+=x&-x)c[x]+=y;}
inline ll ask(int x){ll sum=0;for(;x;x-=x&-x)sum+=c[x];return sum;}
vector<int>u[N];vector<pair<int,int> >w[N];
int get(int x,int *fa){return fa[x]==x?x:fa[x]=get(fa[x],fa);}
int main(){
	scanf("%d",&n);register int x;register ll ans=0,y,z;
	rep(i,1,n)scanf("%d",&x),u[x].push_back(i);
	scanf("%d",&m);rep(i,1,n+1)l[i]=r[i]=i;
	rep(i,1,m)scanf("%d%lld%lld",&x,&y,&z),w[y].push_back(make_pair(x,z));
	rep(i,1,n){
		for(int j=0;j<(int)w[i].size();j++){
			x=w[i][j].first,y=w[i][j].second,z=ask(x);
			if(z>=y)ans+=y;else ans+=z,add(get(x,l)+1,y-z),add(get(x,r),z-y);
		}
		for(int j=0;j<(int)u[i].size();j++)x=u[i][j],l[x]=get(x-1,l),r[x]=get(x+1,r);
	}
	return printf("%lld\n",ans),0;
}
posted @ 2021-06-09 22:24  7KByte  阅读(203)  评论(0编辑  收藏  举报