hdu 5196 DZY Loves Inversions(树状数组,二分法,逆序数)

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 ) (lr) , how many pairs of integers  i  and  j  are there, such that  lijr  and the sequence  b=aiai+1aj  has exactly  k  inversions.

Moreover, DZY has  q  queries.
 

Input
The input consists several test cases.( TestCase5 )

The first line contains three integers  n,q,k(1n105,1q105,0k1018) .

The next line contains  n  positive integers, separated by single spaces,  a1,a2,,an(1ai109) .

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 5
Hint1
query 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的子区间

思路:看了别人代码都做了好久- - ....菜的抠脚。

思路跟官方题解是一样的

考虑如何计算一个区间中有多少个子区间的逆序对数小于等于KK。这样做两遍就能算出恰好等于KK的了。

对于i(1\leq i \leq n)i(1in),我们计算r_iri表示[i,r_i][i,ri]的逆序对数小于等于KK,且r_iri的值最大。显然r_iri单调不降,我们可以通过用两个指针扫一遍,利用树状数组计算出rr数组。

对于每个询问L,RL,R,我们要计算的是\sum_{i=L}^{R}[ \min(R, r_i)-i+1 ]i=LR[min(R,ri)i+1] 由于r_iri具有单调性,那我们直接在上面二分即可,然后记一个前缀和。总复杂度是\text{O}((n+q) \log n)O((n+q)logn)。离线的话的还可以做到\text{O}(n \log n + q)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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值