LibreOJ #3277 [JOISC 2020 Day3] 星座 3 题解

对于这道题来说,我们有两种做法。

首先我们可以确定一点,就是他给我们的建筑是一定到底的,那么肯定不是让我们从左往右枚举的。

这里就以样例来举个例子:

l3277-1.png

我们首先看最上面的这一行。

l3277-2.png

当我们碰到了最高的那个建筑的时候,我们当前这个区间就被分成了两份。

l3277-3.png

随着我们不断向下,我们的区间也在不断细分,直到最后全是建筑。

然后我们就可以以此建立一棵笛卡尔树了。
其中我们的根节点是最上面的那一行再往上的那一片空白区域,我们可以保证其一定是一个完整的区间。

对于每一个区间,我们需要先处理一下,使其只剩一颗星星。
这样可以大大简化我们的冲突判断,减少了对时间的需求。

其次,我们需要对两个(或多个)区间进行合并,就好似对这个区间进行DP,让每一个区间中的那一颗星星选或不选,然后如果选的话就考虑其下面与其冲突的星星,这样这个区间的子树的所有星星都需要删去。
这样一直合并到最上面的区间之后就得到了我们要的结果。

我们可以使用树链剖分来维护上面的信息。
不过下面有个更优的做法,就不放代码了。

我们可以贪心一下。

首先我们将所有房屋和星星按照纵坐标从小到大排个序。
然后我们搞一个并查集,使用并查集维护每一个点可以到达的最大范围。
最后我们建立一棵树状数组,用来存储每一个横坐标中把所有星星清空的代价之和。这一棵树是随着枚举纵坐标不断更新的。

之后我们从下往上处理信息。

对于每一个纵坐标,我们假设已经处理出来所有纵坐标小于其的答案。
我们在树状数组上面查找当前纵坐标所对应的值 S S S,与消除这一颗星星所需要的代价 C C C 比较一下。

  • 如果 S ≥ C S \geq C SC,那就意味着我们加入这颗星星并不会使答案更优,因为靠上的星星可能会冲突的概率更大一些,况且保留其权值又不如保留其下面的星星的权值之和更优。所以我们的答案直接加上 C C C 即可。
  • 如果 S < C S < C S<C,那我们就不能确定保留这一颗星星是否更优了。我们先暂且给答案加上 S S S,然后将当前星星能够到达的横坐标上的每一个值加上 C − S C-S CS。这样我们在继续枚举的时候如果发现留着当前点仍然不优,那么我们就向答案里面又加入了 C − S C-S CS,相当于是向答案中加入了 C C C,而把 S S S 拎了出来。

前者的正确性很显然,后者则可以这样想:
我们已经在递推的时候的每一步都是当前纵坐标的最优解,而我们保留 C C C 相当与就是舍弃了这个最优解。
当我们再想把 C C C 删去而保留 S S S 的时候,选取的 S S S 仍然是那个区间的最优解,就不需要继续考虑更深的点了。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 200010;
struct dsu
{
	int p[N];
	int find(int x)
	{
		if (p[x] != x)p[x] = find(p[x]);
		return p[x];
	}
}L, R;
int n, m;
#define lowbit(x) (x&-x)
ll tr[N];
void add(int x, ll c)
{
	for (int i = x; i <= n; i += lowbit(i))
		tr[i] += c;
}
ll sum(int x)
{
	ll res = 0;
	for (int i = x; i; i -= lowbit(i))
		res += tr[i];
	return res;
}
ll ans;
vector<pair<int, int> >st[N];
vector<int>h[N];
int main()
{
	scanf("%d", &n);
	L.p[n + 1] = R.p[n + 1] = n + 1;
	for (int i = 1; i <= n; i++)
	{
		L.p[i] = R.p[i] = i;
		int x;
		scanf("%d", &x);
		h[x].push_back(i);
	}
	scanf("%d", &m);
	for (int i = 1; i <= m; i++)
	{
		int x, y, c;
		scanf("%d%d%d", &x, &y, &c);
		st[y].push_back(make_pair(x, c));
	}
	for (int i = 1; i <= n; i++)
	{
		for (pair<int, int> s : st[i])
		{
			ll S = sum(s.first);
			if (S > s.second)ans += s.second;
			else
			{
				ans += S;
				add(L.find(s.first) + 1, s.second - S);
				add(R.find(s.first), S - s.second);
			}
		}
		for (int j : h[i])
		{
			L.p[j] = j - 1;
			R.p[j] = j + 1;
		}
	}
	printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值