A : [算法竞赛进阶指南]楼兰图腾
Time Limit:2 Sec Memory Limit:128 MiB
Back Submit Edit
Description
在完成了分配任务之后,西部314来到了楼兰古城的西部。
相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(‘V’),一个部落崇拜铁锹(‘∧’),他们分别用V和∧的形状来代表各自部落的图腾。
西部314在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了N个点,经测量发现这N个点的水平位置和竖直位置是两两不同的。
西部314认为这幅壁画所包含的信息与这N个点的相对位置有关,因此不妨设坐标分别为(1,y1),(2,y2),…,(n,yn),其中y1~yn是1到n的一个排列。
西部314打算研究这幅壁画中包含着多少个图腾。
如果三个点(i,yi),(j,yj),(k,yk)满足1 ≤ i < j < k ≤ n 且 yi > yj, yj < yk,则称这三个点构成V图腾;
如果三个点(i,yi),(j,yj),(k,yk)满足1 ≤ i < j < k ≤ n 且 yi < yj, yj > yk,则称这三个点构成∧图腾;
西部314想知道,这n个点中两个部落图腾的数目。
因此,你需要编写一个程序来求出V的个数和∧的个数。
Input
第一行一个数n
第二行是n个数,分别代表y1,y2……yn
Output
两个数
中间用空格隔开
依次为V的个数和∧的个数
Sample Input
5
1 5 3 2 4
Sample Output
3 4
#include<iostream>
#include<cstring>
using namespace std;
using ll=long long ;
const int maxn=2e5+10;
int a[maxn],c[maxn],l[maxn],r[maxn];//a数组存储原始数据,c数组用于建立树状数组
//l数组用于正序建立树状数组时得到的逆序对(即得到的就是按位置插入然后获得比当前数大的元素一定是在之前插入即形成一个逆序对)
//r数组用于逆序建立树状数组
//总的来说l数组能记录某位置之前比当前位置元素大的个数
//r数组则是记录的是该位置之后比当前位置元素大 的个数
int n;
inline int lowbit(int x)
{
return x&-x;//树状数组中x位置获得的应该移动的位置
}
void add(int x)
{
for(;x<=n;x+=lowbit(x))
c[x]++;
//树状数组存储的是某种特定的前序和
//当插入某一个数时会影响到它后边的前序和而不会影响到之前的前序和
}
//注意这里指的前序和是指前序逆序对的个数和
int ask(int x)
{
int ans=0;
for(;x;x-=lowbit(x))
ans+=c[x];//获得x位置的前序和
return ans;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
l[i]=ask(a[i]);
add(a[i]);
//建立正序树状数组,并得到前序逆序对
}
memset(c,0,sizeof(c));
for(int i=n;i>0;i--)
{
r[i]=ask(a[i]);
add(a[i]);
//建立逆序树状数组,并得到后序逆序对
}
//这里理解获得前序逆序对和后序逆序对是这样的:
//以前序逆序对为例
//因为是按顺序(即位置插入数据的),当判断当前位置的前面有多少个逆序对时
//保存即可因为一定是该位置之前的位置插入的,这样就可以得到前序逆序对了
//同理可以理解后序逆序对的获取
//这里有些错误这里不应该是逆序对
//存储的是正序的,存储的是比当前位置小且元素也小的数据
//这里通过简单的运算也是可以得到逆序对的
//理解思想很重要
ll ans1=0,ans2=0;
for(int i=2;i<=n;i++)
{
ans1+=(ll)(i-l[i]-1)*(n-i-r[i]);
//(i-l[i]-1)得到的是前序逆序对的个数当前位置数减去正序对的个数在减去当前未知的个数
//(n-i-r[i])得到的后序逆序对的个数等于总的个数减去包括当前位置的位置数
//在减去后序正序对的个数即得到后序逆序对的个数
//这里在理解正序逆序时
//都要以当前位置为参考点(即当前位置都左右极限趋近与当前位置数)
ans2+=(ll)(l[i])*r[i];
}
cout<<ans1<<" "<<ans2<<endl;
return 0;
}