inv 线段树,逆序对,离散化

本文介绍了一种解决特定逆序对问题的算法实现,利用线段树进行高效查询和更新,适用于大规模数据处理。通过读取输入文件,对数据进行预处理并排序,最终输出满足条件的数对数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【问题描述】
给定N,以及A1,A2,……AN,求所有的数对(i,j)同时满足:
(1) i<j
(2) 2Ai>Aj
【输入文件】
输入文件 inv.in第一行N
接下来N行,每行一个整数,第i行的整数为Ai
【输出文件】
输出文件inv.out包含一行一个整数,表示满足条件的数对的个数
【输入样例】
3
4 6 8
【输出样例】
2
【样例说明】
数对为(1,2),(2,3)
【数据规模】
N≤100000
1≤Ai≤10N,且所有Ai两两不同

30%数据N≤1000
50%数据N≤10000

注意:
输出的大小可能会超过32位整数

题解:线段树逆序对模板,数据大,离散。

#include<cstdio> 
#include<cstring>
#include<algorithm>
#define N 100005
#define lsn x+x
#define rsn x+x+1
#define lss l,mid
#define rss mid+1,r
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f; 
}
void write(ll x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n,m,a[N],b[N],num[2*N],t[8*N];
ll ans=0;
inline void init()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        b[i]=2*a[i];
        num[i]=a[i];
        num[n+i]=b[i];
    }
    sort(num+1,num+1+n+n);
    m=unique(num+1,num+1+n+n)-num-1;
    for(int i=1;i<=n;i++)
    {
        a[i]=lower_bound(num+1,num+1+m,a[i])-num;
        b[i]=lower_bound(num+1,num+1+m,b[i])-num;
    }
}
void change(int x,int l,int r,int p)
{
    if(l==r&&l==p)
    {
        t[x]++;
        return;
    }
    int mid=(l+r)/2;
    if(p<=mid) change(lsn,lss,p);
    else change(rsn,rss,p);
    t[x]=t[lsn]+t[rsn];
}
int find(int x,int l,int r,int s,int e)
{
    if(l==s&&r==e) return t[x];
    int mid=(l+r)/2;
    if(e<=mid) return find(lsn,lss,s,e);
    else if(s>mid) return find(rsn,rss,s,e);
         else return find(lsn,lss,s,mid)+find(rsn,rss,mid+1,e);
}
int main()
{
    freopen("inv.in","r",stdin);
    freopen("inv.out","w",stdout);
    memset(t,0,sizeof(t));
    init();
    for(int i=1;i<=n;i++)
    {
        ans+=find(1,1,m,a[i]+1,m);
        change(1,1,m,b[i]);
    }
    write(ans);
    return 0;
}
逆序数是指在一个序列中,如果存在一对元素 $i < j$ 且 $a[i] > a[j]$,那么这对元素就构成一个逆序对。整个序列中逆序对的数量称为逆序数。逆序数可以用来衡量一个序列的有序程度,它在算法分析和数据结构研究中具有重要意义。 ### 逆序数的计算方法 1. **暴力法** 最简单的方法是通过双重循环遍历序列,对于每一个元素 $a[i]$,遍历其后面的所有元素 $a[j]$,如果 $a[i] > a[j]$,则计数器加一。这种方法的时间复杂度为 $O(n^2)$,适用于小规模数据集。 例如,对于数组 `[3, 1, 2]`,可以通过双重循环计算逆序数: ```python def count_inversions(arr): count = 0 n = len(arr) for i in range(n): for j in range(i + 1, n): if arr[i] > arr[j]: count += 1 return count ``` 2. **归并排序法** 归并排序法是计算逆序数的经典方法,其时间复杂度为 $O(n \log n)$。归并排序的核心思想是将数组分成两半,分别计算左半部分和右半部分的逆序数,并在合并过程中计算左右两部分之间的逆序数。 归并排序的关键在于合并过程中统计逆序数。当左半部分的某个元素大于右半部分的某个元素时,左半部分当前元素及其之后的所有元素都会与右半部分的当前元素构成逆序对。 代码实现如下: ```python def merge_sort(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, inv_left = merge_sort(arr[:mid]) right, inv_right = merge_sort(arr[mid:]) merged, inv_merge = merge(left, right) total_inv = inv_left + inv_right + inv_merge return merged, total_inv def merge(left, right): merged = [] i = j = inv_count = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) inv_count += len(left) - i j += 1 merged.extend(left[i:]) merged.extend(right[j:]) return merged, inv_count ``` 3. **树状数组(Fenwick Tree)** 树状数组是一种高效的数据结构,可以用于动态维护前缀和。通过树状数组可以在 $O(n \log n)$ 的时间复杂度内计算逆序数。具体步骤如下: - 首先对数组进行离散化,将元素映射到较小的范围。 - 然后从后往前遍历数组,统计当前元素之前有多少个比它小的元素。 代码实现如下: ```python class FenwickTree: def __init__(self, size): self.n = size self.tree = [0] * (self.n + 1) def update(self, index, delta): while index <= self.n: self.tree[index] += delta index += index & -index def query(self, index): result = 0 while index > 0: result += self.tree[index] index -= index & -index return result def count_inversions(arr): # 离散化 sorted_arr = sorted(set(arr)) rank = {v: i+1 for i, v in enumerate(sorted_arr)} ft = FenwickTree(len(rank)) inv_count = 0 for i in reversed(range(len(arr))): r = rank[arr[i]] inv_count += ft.query(r - 1) ft.update(r, 1) return inv_count ``` 4. **线段树** 线段树是一种高效的数据结构,可以用于区间查询和更新操作。线段树也可以用来计算逆序数,时间复杂度为 $O(n \log n)$。具体步骤如下: - 构建线段树,初始化为0。 - 从后往前遍历数组,统计当前元素之前有多少个比它小的元素。 代码实现较为复杂,这里不再赘述。 ### 逆序数的实现步骤 1. **选择算法** 根据数据规模和性能要求选择合适的算法。对于小规模数据可以选择暴力法,而对于大规模数据可以选择归并排序、树状数组或线段树。 2. **处理输入数据** 将输入数据读取到数组中,并进行必要的预处理(如离散化)。 3. **计算逆序数** 根据所选算法实现逆序数的计算逻辑。 4. **输出结果** 将计算得到的逆序数输出。 ### 示例代码 以下是归并排序法的完整实现: ```python def merge_sort(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, inv_left = merge_sort(arr[:mid]) right, inv_right = merge_sort(arr[mid:]) merged, inv_merge = merge(left, right) total_inv = inv_left + inv_right + inv_merge return merged, total_inv def merge(left, right): merged = [] i = j = inv_count = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) inv_count += len(left) - i j += 1 merged.extend(left[i:]) merged.extend(right[j:]) return merged, inv_count # 示例用法 arr = [3, 1, 2] _, inv_count = merge_sort(arr) print(f"逆序数为: {inv_count}") ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值