洛谷題單指南-二叉堆與樹狀陣列-P5677 [GZOI2017] 配對統計

五月江城發表於2024-11-19

原題連結: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;
}

相關文章