比赛
(mat.pas/c/cpp)
【问题描述】
有两个队伍A和B,每个队伍都有n个人。这两支队伍之间进行n场1对1比赛,每一场都是由A中的一个选手与B中的一个选手对抗。同一个人不会参加多场比赛,每个人的对手都是随机而等概率的。例如A队有A1和A2两个人,B队有B1和B2两个人,那么(A1 vs B1,A2 vs B2)和(A1 vs B2,A2 vs B1)的概率都是均等的50%。
每个选手都有一个非负的实力值。如果实力值为X和Y的选手对抗,那么实力值较强的选手所在的队伍将会获得(X-Y)^2的得分。
求A的得分减B的得分的期望值。
【输入格式】
第一行一个数n表示两队的人数为n。
第二行n个数,第i个数A[i]表示队伍A的第i个人的实力值。
第三行n个数,第i个数B[i]表示队伍B的第i个人的实力值。
【输出格式】
输出仅包含一个实数表示A期望赢B多少分。答案保留到小数点后一位(注意精度)。
【样例输入】
2
3 7
1 5
【样例输出】
20.0
【数据规模】
对于30%的数据,n≤50。
对于100%的.据,n≤50000;A[i],B[i]≤50000。
对于30%的数据可以直接n^2枚举,按定义计算,这里不多解释;
100%的数据需要用到数学上的推导,
先分析一个式子,假设(ai-bj)^2,拆开来就是ai^2-2*ai*bj+bj^2;
那么我们由定义:对于每一对ai,bj,若ai>bj则期望得分(ai-bj)^2,否则为-(ai-bj)^2;所以像上面那样拆开式子后会发现:对于同一ai,与所有bj匹配,它得分应该分为三个部分的和:ai^2,2*ai*bj,bj^2;因式分解后,如果对b进行排序,对于ai>bj的前k个数部分,得 k(ai)^2-2*ai*(b1+b2+……+bk)+((b1)^2+……+(bk)^2),对于ai<bj的后(n-k+1)部分,得分为-(n-k+1)*(ai)^2+2*ai*(b[k+1]+b[k+2]+……+b[n])-(b[k+1]^2+……+b[n]^2).
所以容易发现一个优化:b与b^2其实就是数列,我们预处理出它们的前缀和;枚举时只枚举a,而需要算b时就用前缀和算就好了(看看上面因式分解出来的括号,都是连续子序列和的形式,可以前缀和直接求出);但是,如上文所说,要分ai>bj及ai<bj的部分分段讨论,故应找出它们的交界点再用上式,这时用排序+二分就可以了,具体请看代码。
时间复杂度:对于a数组要枚举一遍,b数组因为要排序二分,故算法时间复杂度为O(nlogn);
代码如下:
#include
using namespace std;
int n,ans;
long long a[50001],b[50001],sum[50001],sum1[50001];
void qs(int l,int r)
{
int i=l;
int j=r;
int m=b[(l+r)/2];
while (i<=j)
{
while (b[i]
m) j--;
if (i<=j)
{
int k=b[i];
b[i]=b[j];
b[j]=k;
i++;
j--;
}
}
if (i
l) qs(l,j);
}
int main()
{
freopen("mat.in","r",stdin);
freopen("mat.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
scanf("%d",&b[i]);
qs(1,n);
for (int i=1;i<=n;i++) //只需要计算b与b^2前缀和就可以了
{
sum[i]=sum[i-1]+b[i];
sum1[i]=sum1[i-1]+b[i]*b[i];
}
for (int i=1;i<=n;i++)
{
int l=1;
int r=n;
while (l
b[r]) r++;
ans+=(r-1)*a[i]*a[i]-2*a[i]*sum[r-1]+sum1[r-1]-(n-r+1)*a[i]*a[i]+2*a[i]*(sum[n]-sum[r-1])-(sum1[n]-sum1[r-1]);
}
double tot=ans/double(n);
printf("%.1lf",tot);
return 0;
}