10.19数论模拟赛

Problem 1. pay
Input file: pay.in
Output file: pay.out
Time limit: 1 second
Memory limit: 256 MB
Mr.Hu 开了个饭店,来了两位客人:Alice 和Bob,他们吃完饭要结账时,发现他们需要支付c 元钱,但
是Alice 只有面值为a 的钱,Bob 只有面值为b 的钱(他们每个人的钱的和都大于c, 即可以认为他们有
无数张对应面值的钱)。现在,Mr.Hu 想知道,他们可能刚好支付完饭钱吗?如果可能,那么有多少种方
式?你还需要计算出他们所有可能的支付方式的支付的钱的张数的和。
Input
第1 行包含1 个整数:T opt,其中T 表示数据组数,opt 为数据类型。
接下来T 行,每行3 个整数:a b c。
Output
对于每组数据:
• 如果opt = 1,输出一行,包含一个整数:A,其中A 表示刚好支付的方案数。
• 如果opt = 2,输出一行,包含两个整数:A B,其中A 表示刚好支付的方案数,B 表示所有可能
支付方式的张数和。
Sample
pay.in 
2 2
3 4 21

2 4 12

pay.out

2 13
4 18
样例解释:
对于3 4 21,一共有两种可能的支付方式,分别是:(3; 3); (7; 0)1,所以A 为2,B 为3+3+7+0 = 13。
对于2 4 12,一共有四种可能的支付方式,分别是:(6; 0); (4; 1); (2; 2); (0; 3),所以A 为4,B 为
6 + 0 + 4 + 1 + 2 + 2 + 0 + 3 = 18。
pay.in 
2 1
3 4 21

2 4 12

pay.out

2

4

Note
• 对于20% 的数据,1 a; b; c 10000,1 T 1000;
• 对于另外40% 的数据,1 a; b; c 1e9,其中opt = 1;
• 对于另外40% 的数据,1 a; b; c 1e9,其中opt = 2;
• 对于100% 的数据,1 T 1e5,1 opt 2。

1其中(x; y) 表示Alice 支付x 张面值为a 的钱,Bob 支付y 张面值为b 的钱

题解:

显然扩展欧几里得求得ax+by=c的一组解,然后压缩范围求两个都在非负范围的解集,解集内的x,y肯定是等差变化,第一问求方案数即两个等差数列的长度之和,第二问就把权值加起来即可,自己yy吧。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
ll a,b,c,f,g,x,y,t1,t2;
ll exgcd(ll a,ll b,ll &x,ll &y) {
	if (!b) {
		x=1,y=0;
		return a;
	}
	ll d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return d;
}
inline ll sum(ll st,ll ed,ll num) {
	return (st+ed)*num>>1;
}
inline void cal() {
	f=0,g=0;
	ll n1=x/t1,n2=y/t2;
	f+=n1+n2+1;
	ll p,q;
	p=x+n2*t1,q=y-n2*t2;
	g+=sum(x,p,n2+1)+sum(q,y,n2+1);
	p=x-n1*t1,q=y+n1*t2;
	g+=sum(p,x,n1+1)+sum(y,q,n1+1);
	g-=(x+y);
}
inline int read() {
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
int main() {
	freopen("pay.in","r",stdin);
	freopen("pay.out","w",stdout);
	int kase=read(),opt=read();
	while (kase--) {
		a=read(),b=read(),c=read();
		ll d=exgcd(a,b,x,y);
		if (c%d) {if (opt^1) puts("0 0");else puts("0");continue;}
		t1=abs(b/d),t2=abs(a/d);
		x=x*c/d,y=y*c/d;
		if (x<0) {
			ll temp=(ll)ceil(1.0*(-x)/t1);
			x+=temp*t1,y-=temp*t2;
			if (y<0) {if (opt^1) puts("0 0");else puts("0");continue;}
		}
		else if (y<0) {
			ll temp=(ll)ceil(1.0*(-y)/t2);
			x-=temp*t1,y+=temp*t2;
			if (x<0) {if (opt^1) puts("0 0");else puts("0");continue;}
		}
		cal();
		if (opt==1) printf("%I64d\n",f);
		else printf("%I64d %I64d\n",f,g);
	}
	return 0;
}
Problem 2. sumcomb
Input file: sumcomb.in
Output file: sumcomb.out
Time limit: 1 second
Memory limit: 256 MB
Mr.Hu 被传送到了一个无限大的表格上,现在这个表格(原文Latex格式无法兼容。。。其实就是杨辉三角补成长方形)
现在,Mr.Hu 站在(n;m) 这个位置,他想知道,他向上或向左上方45 度望去,看到的数的和是多少。
从(n;m) 向上望去,他会看到(n;m),(n-1,m),(n-2,m) 这些位置。
从(n;m) 向左上方45 度望去,他会看到(n;m),(n-1,m-1)直到某一维的下标变为0.
这个数可能很大,你只需将答案对1e9 + 7 取模即可。
Input
第1 行一个整数:T,表示数据组数。
接下来T 行,每行格式为:dir n m,其中dir 为1 表示向上看,2 表示向左上方看,(n,m) 为Mr.Hu
现在的位置。
Output
对于每组数据,输出一行表示答案。
Sample
sumcomb.in 
2

1 3 2
2 3 2

sumcomb.out
4

6
表格左上角长成这样(行列都是0 base 的):
1 0 0 0
1 1 0 0
1 2 1 0
1 3 3 1
这样从(3; 2) 向上看,会看到:3 1 0 0,和为4。
向左上角看,会看到:3 2 1,和为6。
Note
• 对于30% 的数据,1 n,m 5000,1 T 1000;
• 对于100% 的数据,1 n,m 106,1 T 50000。

题解:杨辉三角性质。竖着看答案为C(n+1,m+1),斜着看答案为C(n+1,m),用组合数递推公式即可证明,在图上补一项一直往下合并(C(n,m)=C(n-1,m-1)+C(n-1,m))最后再减去这一项其实更容易理解。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e6+4;
const ll MOD=1e9+7;
ll fac[MAXN]={1};
int n,m,opt;
inline int read() {
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
inline ll fpow(ll a,ll b,ll p) {
	ll ret=1;
	while (b) {
		if (b&1) ret=ret*a%p;
		a=a*a%p,b>>=1;
	}
	return ret;
}
inline ll C(ll N,ll M) {
	if (M>N) return 0;
	return fac[N]*fpow(fac[M]*fac[N-M]%MOD,MOD-2,MOD)%MOD;
}
int main() {
	freopen("sumcomb.in","r",stdin);
	freopen("sumcomb.out","w",stdout);
	for (register int i=1;i<MAXN;++i) fac[i]=fac[i-1]*i%MOD;
	int kase=read();
	while (kase--) {
		opt=read(),n=read(),m=read();
		if (m>n) {puts("0");continue;}//组合数判不掉的火坑
		if (opt&1) printf("%I64d\n",C(n+1,m+1));
		else printf("%I64d\n",C(n+1,m));
	}
	return 0;
}

Problem 3. kor
Input file: kor.in
Output file: kor.out
Time limit: 1 second
Memory limit: 256 MB
Mr.Hu 觉得在学习过程中,需要举一反三,做一题要理解透,然后遇到相似的问题时能类似地转化。所
以想了一道和以前类似的题目,相信聪明如你,肯定能轻而易举地解决。
Mr.Hu 会给你n 个非负整数,然后从中选k 个出来,然后把这k 个数按位或起来,Mr.Hu 想知道有多
少种选法,使得或起来的结果为r。
Input
第1 行一个整数T,表示测试组数。
接下来T 组数据,对于每组数据:
第1 行两个整数n k r。
接下来1 行包含n 个非负整数:a1 a2 : : : an。
Output
对于每组数据,输出一行,包含一个整数,即方案数,因为结果可能很大,只需要对109 + 7 取模即可。
Sample
kor.in 
2

4 2 3
1 2 3 4
4 1 1
1 2 3 4

kor.out
3

1
对于第一组数据,一共有3 种选法:(1; 2); (1; 3); (2; 3)。
对于第二组数据,一共有1 种选法:(1)。
Note
• 对于10% 的数据,1 n 10,0 ai < 2^10;
• 对于30% 的数据,1 n 100,0 ai < 2^10;
• 对于50% 的数据,1 n 1e5,0 ai < 2^15;
• 对于100% 的数据,1 n 1e5,0 ai < 2^20,1 k n,1 T 5。

题解:下面是“与”的做法,这道题问“或”,就取反一下同样处理。

考虑容斥原理.
我们用cnt1[S] 表示值为S 的数的个数, 通过它可以计算出cnt2[S], 表示值”
包含”S 的数的个数(a 包含b 当且仅当a&b = b).
这一步可以先枚举一个s, 然后枚举U -s 的子集ss, 将所有cnt1[sjss] 加到
cnt2[s] 中即可, 复杂度(3^k),k 为最大位数.
然后通过cnt2[S] 可以计算出cnt3[S], 表示从原来的n 个数中选择k 个并
取交后, 该值包含S 的方案数. 显然有cnt3[s] = comb(cnt2[s]; k).
然后计算cnt4[S], 表示取k 个数取交后结果为S 的方案数. 和第二步类似,
只是每次从加变成减(从大到小枚举).我们可以将第一步和第三步复杂度优化到O(k*(2^k)).
枚举i 从0 到k - 1, 枚举到i 时, 保证[0; i - 1] 已经加完,[i; k -1] 位还没
有动, 然后枚举U(1 << i) 的子集s, 将sj(1 << i) 加到s 上去.
总的复杂度O(k*(2^k) + n).

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=1e5+4,R=1<<20,MOD=1e9+7;
int n,m,r;
int b[22],a[MAXN];
int cnt[R],fac[MAXN],inv[MAXN];
inline int read() {
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
int fpow(int a,int b,int p) {
	int ret=1;
	while (b) {
		if (b&1) ret=1ll*ret*a%p;
		a=1ll*a*a%p,b>>=1;
	}
	return ret;
}
inline int C(int n,int m) {
	if (m>n) return 0;
	return 1ll*fac[n]*inv[n-m]%MOD*inv[m]%MOD;
}
inline void fix(int &x) {
	if (x<0) x+=MOD;
	if (x>=MOD) x-=MOD;
}
int main() {
	freopen("kor.in","r",stdin);
	freopen("kor.out","w",stdout);
	fac[0]=fac[1]=inv[1]=inv[0]=1;
	for (register int i=2;i<MAXN;++i) fac[i]=1ll*fac[i-1]*i%MOD,inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	for (register int i=2;i<MAXN;++i) inv[i]=1ll*inv[i]*inv[i-1]%MOD;
	int kase=read();
	while (kase--) {
		memset(cnt,0,sizeof(cnt));
		n=read(),m=read(),r=read()^(R-1);
		for (register int i=0;i<n;++i) ++cnt[a[i]=read()^(R-1)];
		for (int i=0;i<20;++i) {
			int st=(R-1)^(1<<i);
			for (int son=st;son;son=(son-1)&st) {
				cnt[son]+=cnt[son|(1<<i)];
				fix(cnt[son]);
			}
			cnt[0]+=cnt[1<<i];
			fix(cnt[0]);
		}
		for (int st=0;st<R;++st)
			cnt[st]=C(cnt[st],m);
		for (int i=0;i<20;++i) {
			int st=(R-1)^(1<<i);
			for (int son=st;son;son=(son-1)&st) {
				cnt[son]-=cnt[son|(1<<i)];
				fix(cnt[son]);
			}
			cnt[0]-=cnt[1<<i];
			fix(cnt[0]);
		}
		printf("%d\n",cnt[r]);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值