题解:洛谷 P5677 [GZOI2017] 配对统计

【题目来源】

洛谷:[P5677 GZOI2017] 配对统计 - 洛谷

【题目描述】

给定 \(n\) 个数 \(a_1,\cdots,a_n\)

对于一组配对 \((x,y)\),若对于所有的 \(i=1,2,\cdots,n\),满足 \(|a_x-a_y|\le|a_x-a_i|(i\not=x)\),则称 \((x,y)\) 为一组好的配对(\(|x|\) 表示 \(x\) 的绝对值)。

给出若干询问,每次询问区间 \([l,r]\) 中含有多少组好的配对。

即,取 \(x,y\)\(l\le x,y\le r\)\(x\not=y\)),问有多少组 \((x,y)\) 是好的配对。

【输入】

第一行两个正整数 \(n,m\)

第二行 \(n\) 个数 \(a_1,\cdots,a_n\)

接下来 \(m\) 行,每行给出两个数 \(l,r\)

【输出】

\(Ans_i\) 表示第 \(i\) 次询问的答案,输出 \(\sum_{i=1}^m\limits Ans_i\times i\) 即可。

【输入样例】

3 2
2 1 3
1 2
1 3

【输出样例】

10

【算法标签】

《洛谷 P5677 配对统计》 #树状数组# #排序# #各省省选# #2017# #贵州# #O2优化#

【代码详解】

// 30分版本
#include <bits/stdc++.h>
using namespace std;

#define int long long  // 使用长整型
const int N = 300005;  // 最大数组大小

int a[N];              // 原始数组,存储数值
int b[N];              // b[i]: 存储a[i]与其他元素的最小差值
int n, m, l, r, x, y;  // n: 数组长度, m: 查询次数, l,r: 查询区间, x,y: 临时变量
int ans;               // 最终结果

signed main()
{
    // 输入数组长度和查询次数
    cin >> n >> m;
    
    // 读入原始数组
    for (int i = 1; i <= n; i++) 
        cin >> a[i];
    
    // 预处理:计算每个元素与其他元素的最小差值
    for (int i = 1; i <= n; i++)
    {
        int mins = 1e9;  // 初始化为极大值
        // 遍历所有其他元素,找到与a[i]的最小差值
        for (int j = 1; j <= n; j++)
        {
            if (i == j) continue;  // 跳过自身
            mins = min(mins, abs(a[i] - a[j]));
        }
        b[i] = mins;  // 存储a[i]的最小差值
    }
    
    // 处理m个查询
    for (int i = 1; i <= m; i++)
    {
        cin >> l >> r;  // 读入查询区间
        int count = 0;   // 当前查询的计数
        
        // 双重循环:遍历区间[l,r]中的所有元素对
        for (int x = l; x <= r; x++)
        {
            for (int y = l; y <= r; y++)
            {
                if (x == y) continue;  // 跳过相同的元素
                
                // 检查条件:|a[x]-a[y]| <= b[x]
                // 即检查a[y]是否在a[x]的"邻域"内
                if (abs(a[x] - a[y]) <= b[x])
                {
                    count++;  // 满足条件,计数加1
                }
            }
        }
        
        // 累加结果:count * 查询编号i
        ans += count * i;
    }
    
    // 输出最终结果
    cout << ans << endl;
    
    return 0;
}
// 50分版本
#include <bits/stdc++.h>
using namespace std;

#define int long long  // 使用长整型
const int N = 300005;  // 最大数组大小

// 结构体:存储数值和原始位置
struct Node
{
    int num;  // 数值
    int id;   // 原始位置(下标)
} a[N];

int b[N][2];        // b[i][0]: 存储i的左侧最近邻元素位置,b[i][1]: 存储i的右侧最近邻元素位置
int n, m, l, r, x, y;  // n: 数组长度, m: 查询次数, l,r: 查询区间
int ans;            // 最终结果

/**
 * 比较函数:按数值升序排序
 */
bool cmp(Node x, Node y)
{
    return x.num < y.num;
}

signed main()
{
    // 输入数组长度和查询次数
    cin >> n >> m;
    
    // 读入数组并记录原始位置
    for (int i = 1; i <= n; i++) 
    {
        cin >> a[i].num; 
        a[i].id = i;  // 记录原始下标
    }
    
    // 按数值升序排序
    sort(a + 1, a + n + 1, cmp);
    
    // 预处理:计算每个元素的最近邻元素
    // 处理第一个元素(只有右侧邻居)
    b[a[1].id][1] = a[2].id;    // 第一个元素的右侧最近邻是第二个元素
    
    // 处理最后一个元素(只有左侧邻居)
    b[a[n].id][0] = a[n - 1].id;  // 最后一个元素的左侧最近邻是倒数第二个元素
    
    // 处理中间元素
    for (int i = 2; i < n; i++)
    {
        // 默认左侧最近邻是前一个元素
        b[a[i].id][0] = a[i - 1].id;
        
        // 比较左右两侧的距离
        int left_dist = a[i].num - a[i - 1].num;    // 到左侧邻居的距离
        int right_dist = a[i + 1].num - a[i].num;   // 到右侧邻居的距离
        
        if (right_dist < left_dist)
        {
            // 右侧邻居更近,清空左侧邻居,设置右侧邻居
            b[a[i].id][0] = 0;           // 清空左侧邻居
            b[a[i].id][1] = a[i + 1].id; // 设置右侧邻居
        }
        else if (right_dist == left_dist)
        {
            // 两侧距离相等,同时记录右侧邻居
            b[a[i].id][1] = a[i + 1].id;
        }
        // 如果左侧更近,则只保留左侧邻居(默认情况)
    }
    
    // 调试输出(注释状态)
    // for (int i = 1; i <= n; i++)
    //     cout << b[i][0] << " " << b[i][1] << endl;
    
    // 处理m个查询
    for (int i = 1; i <= m; i++)
    {
        cin >> l >> r;  // 读入查询区间
        int count = 0;   // 当前查询的计数
        
        // 遍历区间[l,r]中的每个元素
        for (int j = l; j <= r; j++)
        {
            // 检查左侧最近邻是否在查询区间内
            if (b[j][0] >= l && b[j][0] <= r)
                count++;
            
            // 检查右侧最近邻是否在查询区间内
            if (b[j][1] >= l && b[j][1] <= r)
                count++;
        }
        
        // 累加加权结果:计数 × 查询编号
        ans += count * i;
    }
    
    // 输出最终结果
    cout << ans << endl;
    
    return 0;
}
// 100分版本
#include <bits/stdc++.h>
using namespace std;

#define int long long  // 使用长整型
const int N = 3000005;  // 最大数组大小

int n, m, l, r;        // n: 数组长度, m: 查询次数, l,r: 查询区间
int paircount = 1;     // 配对数计数器,从1开始
int trl[N * 2], trr[N * 2];  // 两个树状数组,用于统计区间内的有效对
int ans;               // 最终结果

// 结构体:存储数值和原始位置
struct Node
{
    int num;  // 数值
    int id;   // 原始位置(下标)
} a[N];

// 结构体:存储一对元素的位置
struct Pair
{
    int x;  // 较小位置
    int y;  // 较大位置
} p[N * 2];  // 最多可能有2n个对

// 结构体:存储查询区间
struct Sec
{
    int left;   // 区间左端点
    int right;  // 区间右端点
    int id;     // 查询编号
} s[N];

/**
 * 比较函数:按数值升序排序
 */
bool cmp(Node x, Node y)
{
    return x.num < y.num;
}

/**
 * 比较函数:按Pair的y值(较大位置)升序排序
 */
bool cmp2(Pair x, Pair y)
{
    return x.y < y.y;
}

/**
 * 比较函数:按查询区间的右端点升序排序
 */
bool cmp3(Sec x, Sec y)
{
    return x.right < y.right;
}

/**
 * 添加一对元素到Pair数组
 * @param pos 存储位置
 * @param x 第一个元素位置
 * @param y 第二个元素位置
 */
void addpair(int pos, int x, int y)
{
    p[pos].x = min(x, y);  // 存储较小位置
    p[pos].y = max(x, y);  // 存储较大位置
}

/**
 * 计算lowbit:获取x的最低位的1
 * @param x 输入数值
 * @return x的最低位的1所代表的值
 */
int lowbit(int x)
{
    return x & -x;  // 利用补码性质
}

/**
 * 树状数组更新操作:在Pair的x和y位置分别添加值
 * @param pos Pair的索引
 * @param c 增加的值
 */
void add(int pos, int c)
{
    // 在较小位置x的树状数组中添加值
    for (int i = p[pos].x; i <= n; i += lowbit(i))
        trl[i] += c;
    
    // 在较大位置y的树状数组中添加值
    for (int i = p[pos].y; i <= n; i += lowbit(i))
        trr[i] += c;
}

/**
 * 查询区间[left, right]内的有效对数量
 * @param left 区间左端点
 * @param right 区间右端点
 * @return 有效对数量
 */
int query(int left, int right)
{
    int ans1 = 0, ans2 = 0;
    
    // 查询左端点之前的和
    for (int i = left; i; i -= lowbit(i))
        ans1 += trl[i];
    
    // 查询右端点之前的和
    for (int i = right; i; i -= lowbit(i))
        ans2 += trr[i];
    
    // 返回区间内的数量:右端点的和 - 左端点的和
    return ans2 - ans1;
}

signed main()
{
    // 输入数组长度和查询次数
    cin >> n >> m;
    
    // 特殊情况处理:只有一个元素
    if (n == 1)
    {
        cout << 0;
        return 0;
    }
    
    // 读入数组并记录原始位置
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].num;
        a[i].id = i;
    }
    
    // 按数值升序排序
    sort(a + 1, a + n + 1, cmp);
    
    // 预处理:计算每个元素的最近邻对
    // 处理第一个元素(只有右侧邻居)
    addpair(paircount++, a[1].id, a[2].id);
    
    // 处理最后一个元素(只有左侧邻居)
    addpair(paircount++, a[n].id, a[n - 1].id);
    
    // 处理中间元素
    for (int i = 2; i < n; i++)
    {
        int left_dist = a[i].num - a[i - 1].num;    // 到左侧邻居的距离
        int right_dist = a[i + 1].num - a[i].num;   // 到右侧邻居的距离
        
        if (right_dist < left_dist)
        {
            // 右侧邻居更近
            addpair(paircount++, a[i].id, a[i + 1].id);
        }
        else if (right_dist > left_dist)
        {
            // 左侧邻居更近
            addpair(paircount++, a[i].id, a[i - 1].id);
        }
        else
        {
            // 两侧距离相等,记录两个邻居
            addpair(paircount++, a[i].id, a[i + 1].id);
            addpair(paircount++, a[i].id, a[i - 1].id);
        }
    }
    
    // 按Pair的y值(较大位置)排序,用于离线查询
    sort(p + 1, p + paircount, cmp2);
    
    // 调试输出(注释状态)
    // for (int i = 1; i < paircount; i++)
    //     cout << p[i].x << " " << p[i].y << endl;
    
    // 读入所有查询
    for (int i = 1; i <= m; i++)
    {
        cin >> s[i].left >> s[i].right;
        s[i].id = i;  // 记录查询编号
    }
    
    // 按查询区间的右端点排序,用于离线处理
    sort(s + 1, s + m + 1, cmp3);
    
    // 调试输出(注释状态)
    // for (int i = 1; i <= m; i++)
    //     cout << s[i].left << " " << s[i].right << endl;
    
    // 离线处理查询
    int pos = 1;  // 当前处理的Pair索引
    
    for (int i = 1; i <= m; i++)
    {
        // 将右端点小于等于当前查询右端点的Pair加入树状数组
        while (pos < paircount && p[pos].y <= s[i].right)
        {
            add(pos, 1);  // 将Pair加入树状数组
            pos++;
        }
        
        // 查询当前区间内的有效对数量
        // query(left-1, right) 返回区间[left, right]内的数量
        int count = query(s[i].left - 1, s[i].right);
        
        // 调试输出(注释状态)
        // cout << "count " << count << endl;
        
        // 累加加权结果:计数 × 查询编号
        ans += count * s[i].id;
    }
    
    // 输出最终结果
    cout << ans << endl;
    
    return 0;
}

【运行结果】

3 2
2 1 3
1 2
1 3
10
posted @ 2026-02-19 15:50  团爸讲算法  阅读(2)  评论(0)    收藏  举报