比赛 解题报告

比赛

 (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;
}

      
      
     
     
    
    
   
   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值