【NOIP模拟】超级跳棋
题面
小明是今年超级跳棋比赛的裁判,每轮有三名选手参加,结束时统计的分数一定是正整数,形如 a:b:c。小明的任务是在一块特殊的计分板上展示分数,他一共准备了 块写有正整数x1,x2 ,..,,xn 的卡片,可供填写在 a、b、c 的位置上。此外,小明了解到超级跳棋的规则,他发现 a、b、c 之间最多相差 k倍,例如 c/a>k就是不合法的分数。为了检验他准备得是否充分,你需要计算小明可以在计分板上摆放出多少种不同的分数,即 (a,b,c) 这样的三元组有多少个。
3≤n≤100,000,1≤k,xi≤109
分析
显然,要先对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; }
“Make my parents proud,and impress the girl I like.”