Codeforces Round #697 (Div. 3) E. Advertising Agency组合数 + 费马小定理

原题链接

题意 :

就是给一组数 ,让求选其中的k个数组合出最大的值之和 , 问有多少种组合方法 。

思路

提交的时候WA了好多发 ,第一次知道组合数大的时候需要用费马小定理求组合数 ,否则会错 /(ㄒoㄒ)/~~ 。 先说原理 :

为什么组合数取模要用逆元
首先说明一个事实,你直接算出来一个组合数的结果直接对p取模,结果一定是对的,那么这是对一个计算结果一次取模(但上面的前提是你使用的数据结构能存储得下取模前的结果
但如果我们要通过一个前面取过模的式子递推出其他要取模的式子,而递推式里又存在除法
那么一个很尴尬的事情出现了,假如a[i-1]=100%31=7 a[i]=(a[i-1]/2)%31
a[i]=50%31=19 ,但我们现在只知道a[i-1]=7,如何计算出a[i]=19呢? a[i]=(7/2)%31=3?
其实本来是100是整除2的,但是对31取模后就不能整除了,所以我们要求出在mod 31意义下2的逆元是多少
口算可得,2*16%31=1,所以2的逆元就是16,所以a[i]=(a[i-1]inv(2))%31=716%31=19
那么通过逆元我们就得到了正确的结果

回到该题 , 首先先用桶排统计每个数出现的次数 ,因为数据范围也不大 ,懒得用vector了 ,例如1 2 2 3 3 4 , 需要选择4个数 , 那么先从大到小选择,前面的数都是固定的 , 选择到2 发现有两个2 , 但是此时只需要选择1个数 ,则答案即为(C2,1) ,注意判断特殊情况就行了。

代码 :

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int mod = 1e9 + 7 ;
int a[1010] , b[1010] ;
int n , k , t ;

inline ll qpow(ll a , ll b , ll modd){		//快速幂 
	ll ans = 1 ;
	while(b){
		if(b & 1)	ans = ans * a % modd ;
		a = a * a % modd ;
		b >>= 1 ;
	}
	return ans ;
}
inline ll C(ll n , ll m){			//组合数Cnm的值 
	ll ans1 = 1 , ans2 = 1 , ans3 = 1 ;
	for(ll i = 1 ; i <= n ; i++)
		ans1 = ans1 * i % mod ;
	for(ll i = 1 ; i <= m ; i++)
		ans2 = ans2 * i % mod ;
	for(ll i = 1 ; i <= n - m ; i++)
		ans3 = ans3 * i % mod ;
	return ans1 * qpow(ans2 , mod - 2 , mod) % mod * qpow(ans3 , mod - 2 , mod) % mod ;
	//费马小定理 
}

int main(){
	cin >> t ;
	while(t--){
		cin >> n >> k ;
		memset(a , 0 , sizeof(a)) ;
		memset(b , 0 , sizeof(b)) ;
		for(int i = 1 ; i <= n ; i++){
			cin >> a[i] ;
			b[a[i]]++ ;
		}
		if(k == n){				//相等 , 只有一个 
			cout << 1 <<  endl ;
			continue ;
		}
		ll sum = 0 ;
		int id = 0 ;
		sort(a + 1 , a + 1 + n) ;
		ll cnt = 0 ;		//cnt表示前面选择的多少个数是固定的 
		for(int i = n ; i >= 1 ; i--){
			if(cnt + b[a[i]] < k){
				cnt += b[a[i]] ;
				while(a[i] == a[i-1])	i-- ;
			}
			else{
				id = i ;
				break ;
			} 
		}
		if(cnt + b[a[id]] == k){
			cout << 1 << endl ;
			continue ;
		}
		else{
			int temp = k - cnt ;	//temp表示还剩下几个数要选 	
			cout << C(b[a[id]] , temp) % mod << endl ;
		}	
	}
	return 0 ;
} 

ps : 上分局 , 打到一半 , 系统貌似崩溃了 , CF说不算分了 , 心态崩了 555 /(ㄒoㄒ)/~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值