以下为不影响题意的简化版:
给出一个含有n个元素的集合a,要求在集合a中取所有含有k个元素的子集,定义sum的值为子集中最大值与最小值的差。求sum的值,答案对mod取模。
(这里的集合不同于数学上的集合,即使两个数数值相同也被视作不同的元素)
输入:
第一行三个整数 n,k,mod;
第二行n个整数,表示集合a
输出:
一个整数,sum,对mod取模。
样例输入:
4 2 10007
10 20 30 40
样例输出:
100
样例解释:
六个子集:(10,20)(10,30)(10,40)(20,30)(20,40)(30,40)
数据范围:
30% n<=20
60% n<=5000
100% 1<=k<=n<=100000,0<=a[i]<=1e9,2<=p<=1e9+7,保证p为质数。
心路历程:其实看到这个题10min内就想出了正解,但是缺少必要的优化知识以及对于可能出现的爆int情况的考虑于是写炸了。。。
算法分析:
假设所有的数据都是从小到大排列的,由于集合具有无序性,这并不会影响结果。
很显然,无论每个元素都是几,集合的选定方法都是确定的,每个集合都有一个最大值和一个最小值(这不是废话吗)。
于是我们可以将所有的最大值与最小值分开计算,从第k位到第n位,每个元素都可能成为最大值,那么其他k-1个元素如何选定?假设第x位上的数现在是最大值,那么前面x-1个元素中,必有k-1个元素将成为子集中的其他元素,也就是x-1个数选k-1个数,这是什么?组合数,于是所有最大值的和就求出来了。最小值同理。
以下为不影响思路的代码,为了看着舒服(顺便吐槽这题标程写的略辣鸡),删去了部分以long long为中间结果的计算过程(简单点说就是默认数据不会爆int),所以嘛,想要通过这个题,还要加点强制类型转换。
#include<cstdio> #include<algorithm> using namespace std; int n,k,a[100005],fac[100005],mod,ans; int pw(int a,int b) { int r=1; while(b>0) { if(b&1) r=r*a%mod; a=a*a%mod; b>>=1; } return r; } int c(int n,int m) { return fac[n]*pw(fac[m]*fac[n-m]%mod,mod-2)%mod; } int main() { scanf("%d%d%d",&n,&k,&mod); for(int i=1;i<=n;++i) scanf("%d",&a[i]); fac[0]=1; for (int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod; sort(a+1,a+n+1); for(int i=k;i<=n;++i) ans=(ans+a[i]*c(i-1,k-1)%mod)%mod; for(int i=1;i<=n-k+1;++i) ans=(ans-a[i]*c(n-i,k-1)%mod+mod)%mod; printf("%d",ans); return 0; }