2020NWAFUACM选拔部分题解

如果你点进这一题

解释:

50分做法:dfs暴力枚举每个数选和不选即可。

100分做法:用二项式定理求解

小G在这里稍微证明一下,我们设函数
f ( x ) = ( 1 + a 1 x ) ( 1 + a 2 x ) ⋯ ( 1 + a n x ) f(x) = (1+a_1x) (1+a_2x)\cdots(1+a_nx) f(x)=(1+a1x)(1+a2x)(1+anx)

对它展开,在没有合并同类项的之前,每个项的系数其实都是一个子序列的乘积。
例如 n = 2 n=2 n=2时有:
f ( x ) = ( 1 + a 1 x ) ( 1 + a 2 x ) = 1 + a 1 x + a 2 x + a 1 a 2 x 2 f(x) = (1+a_1x) (1+a_2x) = 1+a_1x+a_2x+a_1a_2x^2 f(x)=(1+a1x)(1+a2x)=1+a1x+a2x+a1a2x2

所以,求所有子序列的和只需要求 f ( 1 ) f(1) f(1)即可。但是注意这上面其实多了一个 1 1 1,这是空的序列的贡献,只需要减去这个1即可。

最后我们只需要求
f ( 1 ) − f ( 0 ) = ( 1 + a 1 x ) ( 1 + a 2 x ) ⋯ ( 1 + a n x ) − 1 f(1) - f(0) = (1+a_1x) (1+a_2x)\cdots(1+a_nx) - 1 f(1)f(0)=(1+a1x)(1+a2x)(1+anx)1

最后注意每一步都要进行取模操作。

code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL MOD = 1e9+7;
const LL N = 1010000;

LL n,a[N],ans = 1;

void solve()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		ans = ans*( (a[i] + 1)%MOD );
		ans %= MOD;
	}
	cout<<( (ans - 1)%MOD + MOD ) % MOD;
}

signed main()
{
	solve();
	return 0;
}

氪金

40分做法:判断输入的数据是否全满足 s i = a i s_i=a_i si=ai或有卡池满足 a i = 0 a_i=0 ai=0分别输出 1 1 1 0 0 0即可。

100分做法:这里能求抽到的概率很麻烦。那么我们求反就好了,也就是求失败的概率。失败就是每次都抽不到,第 i i i次抽不到的概率是 s i − a i s i \frac{s_i-a_i}{s_i} sisiai。然后进行累乘就好了。因为模数是质数,分母的逆元可以用费马小定理得出。
最后成功的概率就是
a n s = 1 − ∏ i = 1 n s i − a i a i ans =1 - \prod_{i=1}^{n} \frac{s_i-a_i}{a_i} ans=1i=1naisiai
注意每一步都要进行取模操作。

code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e6+10000;
const int MOD=1e9+7;
LL s[N],a[N],n;
LL quick(LL a,LL b)
{
	LL res=1;
	while(b)
	{
		if(b&1) res = res*a % MOD;
		a = a*a % MOD;
		b >>= 1;
	}
	return res;
}

void solve()
{
	scanf("%d",&n);
	LL ans=1;
	for(int i=1;i<=n;i++) scanf("%lld",&s[i]);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		ans = ans * (s[i]-a[i]) % MOD * quick(s[i],MOD-2) % MOD;
	}
	cout<<( (1-ans) + MOD ) % MOD<<endl;
}

signed main()
{
	solve();
	return 0;
}

风吹麦浪

30分做法:应用的是哈夫曼树的原理,要求最小值就是每次选取序列中两个最小的值进行相加,最后的到的结果就是最小值,为了方便计算,我们可以使用小根堆或优先队列来计算,每次选取堆顶的值进行相加即可。时间复杂度 O ( n ∗ l o g n ) O(n*logn) O(nlogn)

100分做法:显然两个点合并的结果对答案的贡献肯定会越来越大,我们可以维护两个队列,第一个队列 q 1 q_1 q1 初始是每堆果子的数(从小到大排序),第二个队列 q 2 q_2 q2 初始为空,存放每次合并后的数。虽然 1 < = n < = 1 0 7 1<=n<=10^7 1<=n<=107,但由于 1 < = a i < = 1 0 5 1<=a_i<=10^5 1<=ai<=105,排序我们可以使用桶排序。这里由于排了序, q 1 q_1 q1 队列始终是单调递增的,并且 q 2 q_2 q2队列因为上面一条性质也必然是单调递增的。现在我们知道了它们都有单调性,那么合并时选择的数就很显然了。看 q 1 , q 2 q_1,q_2 q1,q2 队头元素大小关系选择较小的两个数合并,将合并得到的数插入到 q 2 q_2 q2 的队尾。如此循环,直到 q 1 q_1 q1 为空, q 2 q_2 q2 只剩一个元素为止。时间复杂度为 O ( n ) O(n) O(n)

code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL N = 105000;
const LL MOD = 1e6+7;
LL ans = 0;
LL n,a[N];
queue<int>q1,q2;

void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		LL tag;
		scanf("%lld",&tag);
		a[tag]++;
	}
	for(int i=1;i<=100000;i++)
	{
		while( a[i] )
		{
			q1.push(i);
			a[i]--;
		}
	}
	ans = 0;
	for(int k=1;k<n;k++)
	{
		int x,y;
		if((q1.front() < q2.front() && !q1.empty()) || q2.empty())
		{
			x = q1.front();	
			q1.pop();		
		}
		else
		{
			x = q2.front();
			q2.pop();
		}
		
		if((q1.front() < q2.front() && !q1.empty()) || q2.empty())
		{
			y = q1.front();		
			q1.pop();		
		}
		else
		{
			y = q2.front();
			q2.pop();
		}
		ans += (x+y);
		q2.push(x+y);
	}
	printf("%lld",ans);
}

signed main()
{
	solve();
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值