POJ1990 MooFest (线段树)

题目链接: MooFest

大致题意

现在告诉你奶牛 i 的听力为 vi,这表示如果奶牛 j 想说点什么让她听到,必须用高于 vi×dis(i,j) 的音量。因此,如果奶牛 ij 想相互交谈,她们的音量必须不小于 max⁡(vi,vj)×dis(i,j)。其中 dis(i,j) 表示她们间的距离。

现在 N 只奶牛都站在一条直线上了,每只奶牛还有一个坐标 xi。如果每对奶牛都在交谈,并且使用最小音量,那所有 N(N−1)/2 对奶牛间谈话的音量之和为多少?

解题思路

相似题目推荐

线段树 本身这个题是树状数组专题的, 但是由于要维护两个信息, 还是习惯写线段树

我们首先考虑把所有奶牛按照他们的声音从小到大排序. 然后按照这样的顺序依次加入每个奶牛, 同时计算出他与已加入奶牛的音量贡献. 这样我们就可以在O(nlogn)的时间复杂度做出了.

考虑到我们需要维护的信息, 我们线段树应该维护的是坐标轴, 分别记录当前区间的奶牛个数, 以及距离O点(坐标为0)的距离和.

这样当我们当加入一个听力为val, 且位于index位置的奶牛时, 产生的贡献分为两种:
​ ①所有已加入的奶牛, 位于其左侧, 则resleft = val * (left.num * index - left.sum).
​ ②所有已加入的奶牛, 位于其右侧, 则resright = val * (right.sum - right.num * index);

最后结果变化为 resleft + resright.

AC代码

#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E4 + 10;
struct node {
	int l, r;
	ll sum; int num;
}t[N << 2];
void pushup(int x) {
	t[x].sum = t[x << 1].sum + t[x << 1 | 1].sum;
	t[x].num = t[x << 1].num + t[x << 1 | 1].num;
}
void build(int l, int r, int x = 1) {
	t[x] = { l, r , 0, 0 };
	if (l == r) return;
	int mid = l + r >> 1;
	build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int a, int c, int x = 1) {
	if (t[x].l == t[x].r) {
		t[x].num++, t[x].sum += c;
		return;
	}
	int mid = t[x].l + t[x].r >> 1;
	modify(a, c, x << 1 | (a > mid));
	pushup(x);
}
node ask(int l, int r, int x = 1) {
	if (l <= t[x].l and r >= t[x].r) return t[x];
	int mid = t[x].l + t[x].r >> 1;
	node left, right; left = right = { 0, 0, 0, 0 };
	if (l <= mid) left = ask(l, r, x << 1);
	if (r > mid) right = ask(l, r, x << 1 | 1);
	return { 0, 0, left.sum + right.sum, left.num + right.num };
}
int main()
{
	int n; cin >> n;
	build(1, N - 5);

	vector<pair<int, int> > v; // v, index
	rep(i, n) {
		int a, b; scanf("%d %d", &a, &b);
		v.push_back({ a, b });
	}
	sort(v.begin(), v.end());


	ll res = 0;
	rep(i, v.size()) {
		int a = v[i - 1].first, b = v[i - 1].second;
		node left = ask(1, b), right = ask(b, N - 5);
		res += a * (1ll * left.num * b - left.sum);
		res += a * (right.sum - 1ll * right.num * b);
		modify(b, b);
	}
	cout << res << endl;
	return 0;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值