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