洛谷P5094 [USACO04OPEN] MooFest G 加强版 题解
题目链接:P5094 [USACO04OPEN] MooFest G 加强版
题意:
每一年,约翰的 n 只奶牛参加奶牛狂欢节。这是一个全世界奶牛都参加的大联欢。狂欢节包括很多有趣的活动,比如干草堆叠大赛、跳牛栏大赛,奶牛之间有时还相互扎屁股取乐。当然,她们会排成一列嚎叫,来欢庆她们的节日。奶牛们的叫声实在刺耳,以致于每只奶牛的听力都受到不同程度的损伤。现在告诉你奶牛 i 的听力为 vi ,这表示如果奶牛 j 想说点什么让她听到,必须用高于 vi×dis(i,j) 的音量。因此,如果奶牛 i 和 j 想相互交谈,她们的音量必须不小于 max(vi,vj)×dis(i,j)。其中 dis(i,j) 表示她们间的距离。
现在 n 只奶牛都站在一条直线上了,每只奶牛还有一个坐标 xi。如果每对奶牛都在交谈,并且使用最小音量,那所有 n(n−1)2 对奶牛间谈话的音量之和为多少?
输入格式:
第 1 行输入一个整数 n 。
接下来 N 行,每行输入两个数 vi 和 xi ,分别代表第 i 头奶牛的听力和坐标。
输出格式:
输出一个数,代表这 n(n−1)2 对奶牛谈话时的音量之和。
数据范围:
1≤N,Vi,xi≤5×104。
这里讲两种做法。
解法1:树状数组。
注意到本题有两个维度,考虑按 v 顺序排序,然后在 x 上动手脚。
可以发现,现在的难点就变成 dis(i,j) 了,因为 vj≥vi(i<j) 时,一定是当前位置的 j 产生贡献
考虑分类讨论处理 dis(i,j)=|xi−xj|。
当 xj≥xi 时,我们需要统计 j 前面有多少个这样的 i ,然后算一算和啥的就好了,
我们可以维护两个树状数组,分别维护 i 的个数和 ∑xi 。
当 xj<xi 时,我们只需要维护一个前缀和,就可以利用之前维护的东西算出答案。
时间复杂度 O(nlogn)
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(5e4+15))
int n,m,res,S;
struct node{ int v,x; } a[N];
struct BIT
{
int tree[N]; int lowbit(int x) { return x & (-x); }
void add(int x,int v) { for(int i = x; i <= m; i += lowbit(i)) tree[i] += v; }
int qry(int x){ int ans=0; for(int i = x; i; i -= lowbit(i)) ans+=tree[i]; return ans;}
}tr[2];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
for(int i = 1; i <= n; i++){ cin >> a[i].v >> a[i].x; up(m, a[i].x); }
sort(a + 1, a + 1 + n,[](node a, node b) { return a.v < b.v; });
for(int i = 1; i <= n; i++)
{
int num = tr[0].qry(a[i].x), sum = tr[1].qry(a[i].x);
res += (num * a[i].x - sum) * a[i].v;
res += ((S - sum) - (i - num - 1) * a[i].x) * a[i].v;
tr[0].add(a[i].x, 1); tr[1].add(a[i].x, a[i].x); S += a[i].x;
}
cout << res << '\n';
return 0;
}
解法2:分治。
这个解法挺巧妙的,有一点CDQ分治的味道。
还是考虑将物品按 v 顺序排序,然后考虑贡献。
对于区间 [l,r] ,我们钦定区间 [l,mid] 和 [mid+1,r] 都满足 x 为升序排序(此时 v 无序)
由于我们已经排序过了,所以 ∀xi∈[l,mid],∃xj∈[mid+1,r],xi≤xj 。
回到 [l,r] 。考虑固定 j∈[mid+1,r] ,则一定有一个 i 使得 i 是最后一个满足 xi≤xj 的位置。
则区间 [l,j] 的贡献可以以下式计算(定义 S[i,j]=∑t∈[i,j]xt)
Δvj=xj−xl+xj−xl+1+⋯+xj−xi+xi+1−xj+⋯+xmid−xj=i∑k=l(xj−xk)+mid∑k=i+1(xk−xj)=i∑k=lxj−i∑k=lxk+mid∑k=i+1xk−mid∑k=i+1xj=xj×(i−l+1)−S[l,i]+S[i+1,mid]−xj×(mid−i)时间复杂度 O(nlogn)
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(5e4+15))
int n,res,sum[N];
struct node{ int v,x; } a[N],tmp[N];
void solve(int l,int r)
{
if(l >= r) return; sum[l - 1] = 0;
int mid = (l + r) >> 1; solve(l, mid); solve(mid + 1, r);
for(int i = l; i <= r; i++) sum[i] = sum[i - 1] + a[i].x;
int i = l - 1, k = 0;
for(int j = mid + 1; j <= r; j++)
{
while(a[i + 1].x <= a[j].x && i < mid){ tmp[++k] = a[++i]; } tmp[++k] = a[j];
res += a[j].v * ((i - l + 1) * a[j].x - sum[i] + sum[mid] - sum[i] - (mid - i) * a[j].x);
}
while(i < mid) tmp[++k] = a[++i];
memcpy(a + l, tmp + 1, k * sizeof(node));
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i].v >> a[i].x;
sort(a + 1, a + 1 + n,[](node a, node b) { return a.v < b.v; });
solve(1, n); cout << res << '\n';
return 0;
}
参考文献: