开营测试解题报告
——前两题太水了,所以只做了第三题的解题报告
题目:
爆裂吧世界(world/1S/64M)
【题目描述】
给你一个长度为n的数列A,请你计算里面有多少个四元组(a,b,c,d)满足:
a≠b≠c≠d,1≤a<b≤n,1≤c<d≤n,Aa<Ab,Ac>Ad
【输入格式】
输入文件第一行有一个整数N,第二行有N个整数A1,A2⋯An
【输出格式】
输出文件仅一行,为一个整数,表示满足条件的四元组的数量
【输入1】
4
2 4 1 3
【输出1】
1
【输入2】
4
1 2 3 4
【输出2】
0
【数据约定】
15% n<=100
100%n<=50000
A在int范围里
题目大意:就是在题目给出的数列中寻找四个数,他们的位置分别为:A,B,C,D;他们的值分别为: Aa , Ab , Ac , Ad 。这四个数要满足: A≠B≠C≠D , Aa<Ab , Ac>Ad 。问这样的情况在这个数列中有多少种。
一开始看起来似乎很短,很easy。但是要留意的是——四个数不能有任何重复,那就不能单纯的计算顺序对和逆序对再相乘得出答案了。所以这就要用到容斥原理【这是后话。
首先看这道题的数据范围。N<=500000 这很明显就是nlogn级别的算法才能通过啊!所以我们就要用高级一点的方法求出每个数的顺序对或逆序对【其实所谓每个数的顺序对其实就是前面或后面有多少个比他大或小的。
怎么求有多少个数比这个数大呢?我们很容易就想到了N^2的算法。但是不行啊!这道题不允许N^2。那怎么办呢?
其实,要求在这个数列中有多少个数比当前这个数大,用树状数组或者说线段树就可以解决【个人认为树状数组比较好理解。
首先,先记录每一个数的位置信息把它们按照顺序排序。然后按照大小把它们的数值记录起来【这里有一个小细节需要注意,题目没有说这个数列中的数是没有重复的,而且留意题目: 这是很重要的,这就说明了相等是不能成立的。】然后再按照读入顺序放回序列中。【其实bibi了这么多就是为了避免一些重复计算或者一些误差。
然后,然后,然后,就是精髓了【重要的事情说三遍】这里就需要用到树状数组来把前面比当前数小的数的个数提取出来【是不是读起来很奇怪】。
举个例子:假如当前这个数是1,
N=4
。那么就把“1”化成二进制的“1”。把“4”化成二进制的“100”。那么,按照树状数组的原理,只需要把“10”和“100”+1就可以成为
log2
级别的算法。那么,怎么把“10”和“100”+1呢?其实不过就是把最后一个“1”进往前进一位。那么不久直接把最后一个“1”+1不久好了。那么就可以
Num+Num
的最后一个“1”。就可以推导成这样——Num+(Num-(Num&(Num-1)))【看起来很蠢,但是翻译起来就是这样的。
Num
加上
Num
的最后一个“1”。而最后一个“1”就是Num-(Num&(Num-1))。而(Num&(Num-1))就正好是除去最后一个一的
Num
。
举个例子:
Num=111
111−1=110
110&111=110
111−110=1
最后一个“1”就是1
Num=1010
1010−1=1001
1001&1010=1000
1010−1000=10
最后一个“1”就是10
然后,然后就so easy 啦。直接就用容斥原理,先把所有的加起来,然后再把重复的减去【重复的其实就是一个数既成为了A又成为了C或者巴拉巴拉的
附:程序一条
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
struct Tnode
{
int num,pos;
}A[1000005];
int x[1000005],n;
long long tree[1000005];
long long Bsmaller[1000005],Bbigger[1000005];
long long Fsmaller[1000005],Fbigger[1000005];
bool cmp(Tnode a,Tnode b)
{
return a.num<b.num;
}
int Get(int x)
{
int tmp=0;
for (int i=x;i!=0;i=i&(i-1))
tmp+=tree[i];
return tmp;
}
void in(int x)
{
for (int i=x;i<=n;i=i+(i-(i&(i-1))))
tree[i]++;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&A[i].num);
A[i].pos=i;
}
sort(A+1,A+1+n,cmp);
int now=0;
for (int i=1;i<=n;i++)
{
if (i==1||A[i].num!=A[i-1].num)
now++;
x[A[i].pos]=now;
}
memset(tree,0,sizeof tree);
for (int i=1;i<=n;i++)
{
Bsmaller[i]=Get(x[i]-1); //这里的-1和下面的不-1是为了预防重复数字,理解起来就是
Bbigger[i]=(i-1)-(Get(x[i])); //得到这个数之前比这个数小(大)的
in(x[i]);
}
memset(tree,0,sizeof tree);
for (int i=n;i>=1;i--)
{
Fsmaller[i]=Get(x[i]-1);
Fbigger[i]=(n-i)-Get(x[i]);
in(x[i]);
}
int tmpsmall=0,tmpbig=0;
int ans=0;
for (int i=1;i<=n;i++)
{
tmpsmall+=Bsmaller[i];
tmpbig+=Bbigger[i];
}
ans=tmpsmall*tmpbig;
for (int i=1;i<=n;i++)
{
ans-=Bsmaller[i]*Bbigger[i];
ans-=Bsmaller[i]*Fsmaller[i];
ans-=Fsmaller[i]*Fbigger[i];
ans-=Fbigger[i]*Bbigger[i];
}
printf("%d\n",ans);
return 0;
}
原来的:
for (int i=1;i<=n;i++)
{
int now=0;//<---
if (i==1||A[i].num!=A[i-1].num)
now++;
x[A[i].pos]=now;
}
现在的:
int now=0;//<---
for (int i=1;i<=n;i++)
{
if (i==1||A[i].num!=A[i-1].num)
now++;
x[A[i].pos]=now;
}
注:fuck!这条程序我纠结了一个晚上,到底哪里出问题了?最后发现:
都是泪啊!
最后,上篇答应了要讲讲第二题。
论:第二题为什么要注意那个
M<2,000,000
那是因为,如果有了这句话。这就证明所有路都的长度都在
1≤len≤2,000,000
那么,不久得出最多一共就只有
2,000,000
条路吗???那么用个数组存起来直接暴力也不过就
4,000,000
次罢了。所以动点脑子,暴力也是可以满分的。【吐槽:我坐这么远加上白板反光我根本没看见老师些什么东西,考完后我才知道有这东西的。fuck!
END。