DZY Loves Inversions
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 433 Accepted Submission(s): 114
Problem Description
DZY has a array
a
, consisting of
n
positive integers, indexed from 1 to
n
.
Let's denote the number with index i as ai .
DZY wants to count, with a given pair ( l,r ) (l≤r) , how many pairs of integers i and j are there, such that l≤i≤j≤r and the sequence b=aiai+1⋯aj has exactly k inversions.
Moreover, DZY has q queries.
Let's denote the number with index i as ai .
DZY wants to count, with a given pair ( l,r ) (l≤r) , how many pairs of integers i and j are there, such that l≤i≤j≤r and the sequence b=aiai+1⋯aj has exactly k inversions.
Moreover, DZY has q queries.
Input
The input consists several test cases.(
TestCase≤5
)
The first line contains three integers n,q,k(1≤n≤105,1≤q≤105,0≤k≤1018) .
The next line contains n positive integers, separated by single spaces, a1,a2,⋯,an(1≤ai≤109) .
Each of the next q lines has two integers: l,r , representing a query.
The first line contains three integers n,q,k(1≤n≤105,1≤q≤105,0≤k≤1018) .
The next line contains n positive integers, separated by single spaces, a1,a2,⋯,an(1≤ai≤109) .
Each of the next q lines has two integers: l,r , representing a query.
Output
For each query, please print a line containing the answer.
Sample Input
6 4 1 3 1 5 4 2 6 2 4 2 3 3 4 1 5
Sample Output
2 0 1 5Hint1query 1. (2,4), (3,4) are ok. query 2. No such pair. query 3. (3,4) is ok. query 4. (1,2), (1,3), (2,4), (3,4), (4,5) are ok.
题意:给定序列,求区间[l,r]中有多少个逆序数为k的子区间
思路:看了别人代码都做了好久- - ....菜的抠脚。
思路跟官方题解是一样的
考虑如何计算一个区间中有多少个子区间的逆序对数小于等于K。这样做两遍就能算出恰好等于K的了。
对于i(1≤i≤n),我们计算ri表示[i,ri]的逆序对数小于等于K,且ri的值最大。显然ri单调不降,我们可以通过用两个指针扫一遍,利用树状数组计算出r数组。
对于每个询问L,R,我们要计算的是∑i=LR[min(R,ri)−i+1] 由于ri具有单调性,那我们直接在上面二分即可,然后记一个前缀和。总复杂度是O((n+q)logn)。离线的话的还可以做到O(nlogn+q),都是非常优秀的。
首先我们用把原数组离散化,然后求出在逆序数<=k的条件下以i点为区间的左端点最多可以延伸到哪个点(右端点记为far[i])
显然far数组是单调不降的(左端点右移的话,逆序数要么不变要么减少,所以far[i]>=far[i-1])
然后利用树状数组求逆序数的模型我们可以求出far数组
然后难点来了。 在有far数组的情况下我们如何在O(logn)的时间复杂度内解决每一个区间询问
首先我们维护前缀和,qian[i]保存左端点>=i点的所有区间的逆序数<=k的数量
然后每次二分找出第一个far[i]>=右端点的点
进行求和即可
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 100050
long long c[N],a[N],l[N],r[N],far[N],sum2[N];
long long ans[N],qian[N],sum1[N];
int n,m;
struct Node
{
int id,v;
} p[N];
int lowbit(int x)
{
return x&-x;
}
void add(long long x,int v)
{
for(int i=x; i<=n; i+=lowbit(i))
c[i]+=v;
}
long long get(int x)
{
long long ans=0;
for(int i=x; i; i-=lowbit(i))
ans+=c[i];
return ans;
}
bool cmp(Node a,Node b)
{
return a.v<b.v;
}
void solve(long long k,int v)
{
if(k<0) return;
memset(c,0,sizeof(c));
long long sum=0;
int rr=0;
for(int i=1; i<=n; i++)
{
while(rr<=n&&sum<=k)
{
rr++;
if(rr>n) break;
sum+=(rr-i)-get(a[rr]);///新加进r点的逆序数等于r点左边比a[r]大的数
add(a[rr],1);///添加a[i]点
}
far[i]=rr-1;
add(a[i],-1);///将a[i]点删除
sum-=get(a[i]-1);///现在树状数组里面<=a[i]的数的个数,即在i点右边且比i点小的数的个数
}
qian[0]=0;
for(int i=1;i<=n;i++)
qian[i]=qian[i-1]+(far[i]-i+1);
for(int i=1;i<=m;i++)
{
int x=l[i],y=r[i];
int pos=lower_bound(far+x,far+y+1,y)-far-1;
ans[i]+=v*(qian[pos]-qian[x-1]+sum2[y-pos]);
}
}
int main()
{
long long k;
sum2[0]=0;
for (int i=1;i<N;i++)
sum2[i] = sum2[i - 1] + i;
while(~scanf("%d %d %lld",&n,&m,&k))
{
for(int i=1; i<=n; i++)
{
scanf("%lld",&p[i].v);
p[i].id=i;
}
sort(p+1,p+1+n,cmp);
int cnt=1;
a[p[1].id]=1;
for(int i=2; i<=n; i++)
{
if(p[i].v!=p[i-1].v) cnt++;
a[p[i].id]=cnt;
}
for(int i=1; i<=m; i++)
scanf("%lld %lld",&l[i],&r[i]);
memset(ans,0,sizeof(ans));
solve(k,1);
solve(k-1,-1);
for(int i=1; i<=m; i++)
printf("%lld\n",ans[i]);
}
return 0;
}