洛谷题单指南-二叉堆与树状数组-P5677 [GZOI2017] 配对统计

原题链接: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;
}

 

posted @ 2024-11-19 16:26  五月江城  阅读(17)  评论(0编辑  收藏  举报