球场大佬 [思维]

题面:

        每天下午,古猴都会去打羽毛球。但是古猴实在是太强了,他必须要到一些比较强的场去打。但是每个羽毛球场都有许多的人排着队,每次都只能上四个人,每个人都有自己的能力值,然而这四个人的总能力的高低与否才是古猴是否决定参加这个场的关键。
        每四个人的总能力值的定义是:任意选两个与另两个PK,能力值的贡献是较高的一组减去较低的一组。比如能力值为5和7的去PK6和10的差值,那么用较高的减去较低的就是6+10-5-7=4。然后四个人的总能力值要任意两两之间与其他两个的总贡献。如(a,b,c,d)四个人,那么他们的总能力值就是(a,b)一组与(c,d)一组PK,(a,c)一组与(b,d)一组PK,(a,d)一组与(b,c)一组PK,(b,c)一组与(a,d)一组PK,(b,d)一组与(a,c)一组PK,(c,d)一组与(a,b)一组PK这六项PK差值和就是四个人的总能力值。
        现在,古猴想知道这个场任意四个人的总能力值的和是多少,但是急着要拿拍,你需要马上告诉他这个场的情况?

输入:

        第一行一个T,接下来T组数据,每组数据第一行一个n,第二行n个整数a[i]表示每个人的能力值,a[i]∈[0,10^9]。

输出:

        输出一个整数表示任意四个人的总能力值。最后答案对 10^9+7 取模。 

样例输入:

3 
4 
1 2 3 3 
5 
1 2 3 4 5
6 
9 18 28 23 12 9

样例输出:

10 
76 
1176

约束条件:

对于 30%的数据 n≤50,T≤5 
对于另外 20%的数据 n≤200,T≤10 
对于另外 50%的数据 n≤2000,T≤100 
保证所有的 n 加起来不超过 2000 

题解:

        约束条件不允许我们 O(N^4) 暴力,我们可以换个思路。因为题目里是要求两两一组的,并且每一组会跟其他的不同组进行PK,所以我们考虑先记录上所有可能的两两一组的能力值和,再去考虑这每一组对答案的贡献。

        代码中我们用数组 b 存储所有可能的两两一组的能力值和(记录各个人能力值的数组 a 也从小大到排),并从小大到排序,其中 cnt 表示所有可能的总组数。容易发现,对于每一组 b[i],在PK中它会作为被减数 i-1 次,减数 cnt-i 次,然而,也不能忽视因为在统计所有可能的两两一组中产生的重复现象,例如出现 {a[x] , a[y]} 和{a[x] , a[z]}的情况,总不能三个人打双打吧。所以考虑四种重复的情况。

        第一种:如图,假定选取 a[x] 和 a[y](绿色线) ,由上推导可得 a[1],a[2],a[3]……a[x-1] 与 a[y] 的组合的能力值都比我们选取的组合的能力值小,且都有 a[y] 这个值重复(红色线),所以{a[x] , a[y]} 这个组合作为被减数出现的次数应当减去 x-1 次。

         第二种:如图,假定仍是选取 a[x] 和 a[y](绿色线) ,由上推导可得 a[2],a[3]……a[y-1],a[y-1] 与 a[x] 的组合的能力值都比我们选取的组合的能力值小,且都有 a[x] 这个值重复(红色线),所以{a[x] , a[y]} 这个组合作为被减数出现的次数应当再减去 y-2 次。

         第三和第四种情况就是我们选定的组合作为减数的情况,同理易得,作为减数出现的次数应当减去 n-x-1+n-y 次。

代码:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
#define endl '\n';
typedef long long ll;
const int mod=1e9+7;
int T,n; 
struct node{
    ll x,y,w;
}b[4000005];
bool cmp(node a,node b) { return a.w<b.w; }
signed main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    cin>>T;
    while(T--)
    {
        cin>>n;
        vector<int> a(n+1);       
        for(int i=1;i<=n;i++) cin>>a[i];
        sort(a.begin(),a.end());
        ll cnt=0,ans=0;
        for(int i=1;i<n;i++)
            for(int j=i+1;j<=n;j++)
            {
                b[++cnt].w=a[i]+a[j];
                b[cnt].x=i;
                b[cnt].y=j; 
                // x 和 y 分别记录每个组合的两个人在原数组中的下标    
            }           
        sort(b+1,b+cnt+1,cmp);
        // 同推导
        for(int i=1;i<=cnt;i++)
            ans=(b[i].w%mod*(i-1-(b[i].x-1+b[i].y-2))%mod-b[i].w%mod*(cnt-i-(n-b[i].x-1+n-b[i].y))%mod+ans+mod)%mod;
        cout<<ans*2%mod<<endl;
    }
    return 0;
}
/**************************************************************
    Problem: 15519
    User: 2021UPC016
    Language: C++
    Result: 正确
    Time:1246 ms
    Memory:95932 kb
****************************************************************/

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值