题目:HDU 4911/poj 2299
第一反应是暴力扫一遍,时间复杂度O(N^2),这在处理10^5的数量级的数据时一定会超时。以下给出两种将时间复杂度优化至O(NlogN)的方法
以HDU 4911为例,求逆序数的模板题
方法1,归并排序法:
在区间[l,r]上,mid=(l+r)>>1;
将两串已经排好序的序列a[l...m]与a[m+1...r]进行归并排序。设l<=i<=mid
当a[j]>=a[i]时,后面的数大于前面的数,不产生逆序对,直接进行归并即可(tmp[k++]=a[i++]),当a[i]>a[j]时,表示前面的数大于后面的数,产生了逆序对。此时i~m的数均比a[j]大,即,产生m-i+1个逆序对。故逆序对总数应加上(m-i+1)
代码:
//By Sean Chen
#include
#include
#include
#include
#include
using namespace std;
int n,k;
long long cnt;
int a[100005],tmp[100005];
void Merge(int head, int mid, int tail) //二路归并
{
int i = head, j = mid + 1, k = head;
while (i <= mid && j <= tail)
{
if (a[i] > a[j]) //前面的数大于后面的数,将后面的数加入归并后的序列,并增加逆序对
{
cnt += mid - i + 1;
tmp[k++] = a[j++];
}
else //后面的数大于前面的数,直接将前面的数加入归并后的序列
{
tmp[k++] = a[i++];
}
}
while (i <= mid)
{
tmp[k++] = a[i++];
}
while (j <= tail)
{
tmp[k++] = a[j++];
}
for (int i = head; i <= tail; i++)
{
a[i] = tmp[i];
}
return;
}
void Merge_Sort(int head, int tail)
{
if (tail > head)
{
int mid = (head + tail) >> 1;
Merge_Sort(head, mid);
Merge_Sort(mid+1, tail);
Merge(head, mid, tail);
}
return;
}
int main()
{
while (scanf("%d%d",&n,&k) != EOF)
{
for (int i = 0; i < n; i++)
{
scanf("%d",&a[i]);
}
cnt = 0;
Merge_Sort(0, n-1);
if (cnt <= k)
cout << 0 << endl;
else
cout << cnt-k << endl;
}
return 0;
}
方法二:线段树+离散化
通过线段树,不断统计当前数字之前比其大的数字的个数,并将当前数字加入线段树,直至最后一个即可。方法较为容易理解,在暴力的基础上,对统计个数的方法进行了优化。注意,由于数字大小到达了10^9,所以需要进行离散化。
//By Sean Chen
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 500500;
int n, a, b, in[maxn], tt[maxn], tmp[maxn], f[maxn], k, q;
long long num[maxn*3];
int bs(int v, int x, int y) //二分查找当前的数字的编号
{
while(x < y)
{
int m = (x+y) >> 1;
if(tmp[m] >= v)
y = m;
else
x = m+1;
}
return x;
}
void build(int pos, int l, int r)
{
num[pos] = 0;
if(l == r) return ;
int m = (l+r) >> 1;
build(pos<<1, l, m);
build(pos<<1|1, m+1, r);
}
void update(int pos, int l, int r)
{
if(l == r)
{
num[pos]++;
return ;
}
int m = (l+r) >> 1;
if(a <= m)
update(pos<<1, l, m);
else
update(pos<<1|1, m+1, r);
num[pos] = num[pos<<1] + num[pos<<1|1];
}
long long query(int pos, int l, int r)
{
if (a <= l && r <= k-1)
return num[pos];
int m = (l+r) >> 1;
long long ans = 0;
if (a <= m)
ans += query(pos<<1, l, m);
if (m < k-1)
ans += query(pos<<1|1, m+1, r);
return ans ;
}
int main()
{
while(scanf("%d%d",&n,&q)!=EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d",&in[i]);
tt[i] = in[i]; //记录原序列
}
sort(in, in+n);
k = 0;
tmp[k++] = in[0];
for(int i = 1; i < n; i++) //离散化
if(in[i] != in[i-1])
tmp[k++] = in[i];
for(int i = 0; i < n; i++)
{
f[i] = bs(tt[i], 0, k-1);
}
long long ans = 0;
build(1, 0, k-1);
for(int i = 0; i < n; i++) //逐个统计之前比他大的数并加入线段树
{
a = f[i] + 1;
ans += query(1, 0, k-1);
a = f[i];
update(1, 0, k-1);
}
if (ans > q)
cout << ans-q << endl;
else
cout<< 0 <
代码: