原题链接:https://www.luogu.com.cn/problem/P5677
题意解读:所谓好的配对,通过分析公式∣ax−ay∣≤∣ax−ai∣(i≠x),可以得知就是一个ax与其差的绝对值最小的形成的配对,在数轴上就是距离ax最近的点ay,配对是下标(x,y),给定若干个区间[l,r],每个区间的配对数*区间编号的累加。
解题思路:
1、求出所有的好配对
先将所有数按值从小到大排序,同时保留其索引编号struct Node{int val, id;} a[N]
对于每一个数,好配对的另一半要么是左边相邻的数,要么是右边相邻的数,取相差更小的,如果相差一样则都是好配对
特别的,第一个数和最后一个数都只有一个好配对
把得到的好配对(x ,y)都存入一个结构体数组struct Pair{int x, y;} p[2 * N]
2、计算一个区间里的好配对数
要计算区间[l, r]内的好配对数,暴力的方法是直接统计出(x,y)在区间内的配对个数,时间复杂度是O(n)
3、计算所有区间里的好配对数 * 区间编号,并累加
上述方法,m个区间需要O(n * m)
既然是统计区间里配对的个数,能否利用前缀和进行优化?答案是肯定的!
第一步:先对所有好配对按右端点y值从小到大排序
第二步:对所有询问按区间右端点r值从小到大排序
第三步:遍历所有询问,对于每一个询问[l,r],将所有右端点不超过r的配对加入树状数组tr[],对所有符合要求好配对执行树状数组修改操作add(x, 1)
(注:tr[i]表示左端点在i ~ i - lowbit(i) + 1范围内所有好配对数,可以认为原数组s[i]表示左端点i的好配对数)
第四步:计算[l,r]范围好配对数,即当前所有s[i] (i >= l)之和,用树状数组操作即: 当前已加入树状数组的所有配对数 - sum(l - 1)
第五步:区间好配对数还要记得乘以查询区间的编号,查询区间可以用结构体数组保存
100分代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 300005;
int n, m;
//所有数和对应编号
struct Node
{
int val, id;
bool operator < (const Node &b) const &
{
return val < b.val;
}
} a[N];
//所有好配对
struct Pair
{
int x, y;
bool operator < (const Pair &b) const &
{
return y < b.y;
}
} p[2 * N];
int cnt;
void addPair(int x, int y) //添加配对时,总是把小的作为左边界,大的作为右边界,方便后续的查询
{
p[++cnt].x = min(x, y);
p[cnt].y = max(x, y);
}
//所有询问和对应编号
struct Ques
{
int l, r, id;
bool operator < (const Ques &b) const &
{
return r < b.r;
}
} q[N];
//树状数组,tr[i]表示左端点是i-lowbit(i)+1 ~ i的好配对个数
int tr[N];
int lowbit(int x)
{
return x & -x;
}
void add(int x)
{
for(int i = x; i <= n; i += lowbit(i)) tr[i] += 1;
}
int sum(int x)
{
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
int main()
{
cin >> n >> m;
if(n == 1)
{
cout << 0;
return 0;
}
for(int i = 1; i <= n; i++)
{
cin >> a[i].val;
a[i].id = i;
}
//按元素值从小到大排序
sort(a + 1, a + n + 1);
//计算好配对
addPair(a[1].id, a[2].id);
addPair(a[n].id, a[n - 1].id);
for(int i = 2; i < n; i++)
{
int dis1 = a[i].val - a[i - 1].val;
int dis2 = a[i + 1].val - a[i].val;
if(dis1 < dis2) addPair(a[i].id, a[i - 1].id);
else if(dis1 > dis2) addPair(a[i].id, a[i + 1].id);
else addPair(a[i].id, a[i - 1].id), addPair(a[i].id, a[i + 1].id);
}
//好配对按右端点从小到大排序
sort(p + 1, p + cnt + 1);
//读入询问
for(int i = 1; i <= m; i++)
{
cin >> q[i].l >> q[i].r;
q[i].id = i;
}
//询问按右端点从小到大排序
sort(q + 1, q + m + 1);
//计算答案
LL ans = 0;
int j = 0;
for(int i = 1; i <= m; i++)
{
//对于每一个询问q[i],将右端点小于等于q[i].r的好配对数加入树状数组
while(j + 1 <= cnt && p[j + 1].y <= q[i].r)
{
add(p[++j].x);
}
ans += 1ll * (j - sum(q[i].l - 1)) * q[i].id;
}
cout << ans;
return 0;
}
标签:val,P5677,int,洛谷题,id,端点,区间,配对,GZOI2017 From: https://www.cnblogs.com/jcwy/p/18554794