(one data one problem)hdu-5792 World is Exploding (树状数组)

题目链接:点击打开链接

Problem Description
Given a sequence A with length n,count how many quadruple (a,b,c,d) satisfies:  abcd,1a<bn,1c<dn,Aa<Ab,Ac>Ad .
 

Input
The input consists of multiple test cases. 
Each test case begin with an integer n in a single line.

The next line contains n integers  A1,A2An .
1n50000
0Ai1e9
 

Output
For each test case,output a line contains an integer.
 

Sample Input
  
  
4 2 4 1 3 4 1 2 3 4
 

Sample Output
  
  
1 0
题目大意:给出一个长为n的序列,问有多少四元组(a,b,c,d)满足a!=b!=c!=d,1<=a<b<=n

题解:

首先要找到这样的四元组是怎么来的,可以先把四元组拆分一下那就是两个二元组,二元组就好求了。就是求一个两个逆序数。那么一个升序,一个降序的二元组的个数相乘就是四元组的了。但是这样乘的话肯定会有用到重复的数字的。因为根据题目要求可以在求逆序数的时候让a!=b。所以在乘的过程中相等会有a=c,a=d,b=c,b=d这四种情况。那么怎么去重以及怎么求呢?

对于第i个数,可以求出它的左面比他大的数的个数,左面比他小的个数,右面比他小的和比他大的。在求升序对的时候就是找出每个比i小的加起来就可以了。那么降序就是左面比他大的呗!相乘就是总的四元组的个数(没有去重的情况下的)。

现在去重。比如先看一下当a==c的时候,那么也就是a和c此时是一个数,那么看看b和d放在什么位置呢?

 因为b>a,所以b在a的右面,d要大于c所以d也要在c的右面。也就是a(c),(b,d),回到题意,如果把a(c)作为第i个值得话。Ai<Ab(升序),,Ai>Ad(降序)。可以看出这是重复的部分。要把它去掉,也就是把i的右面比它大的(以i升序的部分)乘以 右面比它小的(以i降序的部分)。同理其他的也只这样处理。

#include <iostream>
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

LL a[50050],c[50050];
LL b[50050];
LL lmx[50050],rmx[50050],lmn[50050],rmn[50050];
map<LL,LL>mp;
void add(LL i,LL x,LL n)
{
    while(i<=n)
    {
        c[i]+=x;
        i+=i&(-i);
    }
}
LL sum(LL i)
{
    LL s=0;
    while(i>=1)
    {
        s+=c[i];
        i-=i&(-i);
    }
    return s;
}
int main()
{
    LL n;
    while(~scanf("%lld",&n))
    {
        LL ans=0;
        mp.clear();
        for(LL i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            b[i]=a[i];
            //if(!mp[a[i]])mp[a[i]]=++ans;
        }
        sort(b+1,b+n+1);
        for(LL i=1;i<=n;i++)
        {
            if(!mp[b[i]])mp[b[i]]=++ans;
        }
        for(LL i=1;i<=n;i++)
        {
            a[i]=mp[a[i]];
            //cout<<a[i]<<" ";
        }
        //cout<<endl;
        LL oto=0;
        memset(c,0,sizeof(c));
        for(LL i=1;i<=n;i++)
        {
            lmn[i]=sum(a[i]-1);
            lmx[i]=sum(ans)-sum(a[i]);
            oto+=lmn[i];
            add(a[i],1,ans);
            //cout<<lmn[i]<<" "<<lmx[i]<<endl;
        }
        //cout<<"---------------"<<endl;
        memset(c,0,sizeof(c));
        for(LL i=n;i>=1;i--)
        {
            rmn[i]=sum(a[i]-1);
            rmx[i]=sum(ans)-sum(a[i]);
            add(a[i],1,ans);
            //cout<<rmn[i]<<" "<<rmx[i]<<endl;
        }
        //cout<<"-------------------"<<endl;
        LL ant=0;
        for(LL i=1;i<=n;i++)
        {
            ant+=oto*lmx[i];
        }
        ///a=c;
        for(LL i=1;i<=n;i++)
        {
            ant-=(rmx[i]*rmn[i]);
        }
        ///a=d;
        for(LL i=1;i<=n;i++)
        {
            ant-=(rmx[i]*lmx[i]);
        }
        ///b=c;
        for(LL i=1;i<=n;i++)
        {
            ant-=(lmn[i]*rmn[i]);
        }
        ///b=d;
        for(LL i=1;i<=n;i++)
        {
            ant-=(lmn[i]*lmx[i]);
        }
        printf("%lld\n",ant);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值