【NOIP模拟】超级跳棋

时间:2021-12-31 20:43:23

题面

小明是今年超级跳棋比赛的裁判,每轮有三名选手参加,结束时统计的分数一定是正整数,形如 a:b:c。小明的任务是在一块特殊的计分板上展示分数,他一共准备了 块写有正整数x1,x2 ,..,,xn 的卡片,可供填写在 a、b、c 的位置上。此外,小明了解到超级跳棋的规则,他发现 a、b、c 之间最多相差 k倍,例如 c/a>k就是不合法的分数。为了检验他准备得是否充分,你需要计算小明可以在计分板上摆放出多少种不同的分数,即 (a,b,c) 这样的三元组有多少个。

3n100,0001kxi109

分析

显然,要先对x排序去重并计算每个x出现的次数,对于每个xi我们可以扫描求出最大xj

我们就需要计算xi~xj这一段中的数对答案的贡献。

我们其实只需要把数分两类,一类是个数为1,一类是个数为2。

当个数等于3的时候,其实就是在个数为1的答案上加一。

比如 1 1 1 2 3这个序列中,假设k=3。1 1 2 ,1 1 3, 1 2 3都可以被选,有3个1,最多只能在有两个1的情况下增加 1 1 1这种排列

而当个数大于3个的时候,就不会再有更多贡献了 

比如 1 1 1 1 2 3这个序列,假设k=3。在上面四种以外,即使多了一个1也没有其他方案了。

因此每个数出现的次数只需要分两类统计。

假设现在xi必须取

对于xi+1~xj中的数,设出现了的数个数为s,相当于在s个数中选2个,与xi一起全排列

对答案的贡献就是 C(s,2)*2*3=s*(s-1)*3

当xi出现至少两次时,又相当于在s个数中选1个,与2个xi一起全排列,则有C(s,1)*3=s*3

最后加上出现了两次以上的数的个数c2,被选两次的答案c2*3,以及xi有没有>=2多出来的1

代码

#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define ll long long
ll n,k,m,v,s,c1,c2,ans,pos;
ll x[N],tmp[N];
map<ll,ll>mp;
int main()
{
    scanf("%lld%lld",&n,&k);
    for(ll i=1;i<=n;i++)scanf("%lld",&x[i]),mp[x[i]]++;
    sort(x+1,x+1+n);
    m=unique(x+1,x+1+n)-(x+1);
    for(ll i=1;i<=m;i++)
    {
        while(pos<m&&x[pos+1]<=x[i]*k)
        {
            pos++;
            if(mp[x[pos]]>=2)c2++;
            else c1++;
        }
        if(mp[x[i]]>=2)v=1;
        else v=0;
        ans+=(c2-v)*3;
        s=c1+c2-1;
        if(s>1)ans+=s*(s-1)*3;
        if(mp[x[i]]>=2)ans+=s*3;
        if(mp[x[i]]>=3)ans++;
        if(mp[x[i]]>=2)c2--;
        else c1--;
    }
    printf("%lld\n",ans);
    return 0;
}