2024.9.27校测

T1

题目描述

Mr.Hu 开了个饭店,来了两位客人:AliceBob,他们吃完饭要结账时,发现他们需要支付 c 元钱,但是 Alice 只有面值为 a 的钱,Bob 只有面值为 b 的钱(他们每个人的钱的和都大于 c, 即可以认为他们有无数张对应面值钱)。现在,Mr.Hu 想知道,他们可能刚好支付完饭钱吗?如果可能,那么有多少种方式?你还需要计算出他们所有可能的支付方式的支付的钱的张数的和。

输入格式

1 行包含 2 个整数:T,opt,其中 T 表示数据组数,opt 为数据类型。

接下来 T 行,每行 3 个整数:a,b,c

输出格式

对于每组数据:

  • 如果 opt=1,输出一行,包含一个整数:A,其中 A 表示刚好支付的方案数。

  • 如果 opt=2,输出一行,包含两个整数:A,B,其中 A 表示刚好支付的方案数,B 表示所有可能
    支付方式的张数和。

输入样例1

2 2
3 4 21
2 4 12

输出样例1

2 13
4 18

样例1解释

对于 3,4,21,一共有两种可能的支付方式,分别是:(3,3),(7,0)1,所以 A2B3+3+7+0=13

对于 2,4,12,一共有四种可能的支付方式,分别是:(6,0),(4,1),(2,2),(0,3),所以 A4B
6+0+4+1+2+2+0+3=18

输入样例2

2 1
3 4 21
2 4 12

输出样例2

2
4

数据规模

对于 20% 数据,1a,b,c10000,1T1000

对于另外 40% 数据,1a,b,c109,其中 opt=1

对于另外 40% 数据,1a,b,c109,其中 opt=2

对于 100% 数据,1T105,1opt2

题解

Alice 要支付的钱的张数为 x (x0),Bob 要支付的钱的张数为 y (y0),那么可以写出如下二元一次不定方程 ax+by=c,根据数论学习笔记(一)(2024.7.25),当且仅当 gcd(a,b)c 时才有解。

考虑 xy 都要大于 0,那么将 x 变为最小的满足条件的非负整数时,如果 y 仍未非正整数,那么也是凑不出这么多钱的。

以上两种情况是无解的,直接输出 0,如果有 xy 都为非负整数的解,那么考虑通解 x=x0+(b/d)n,y=y0(a/d)n

考虑到将 x 变为最小值时 y 已是最大值,所以 ya/d+1 (加 1 是因为可以一个 a/d 也不减) 就是方案数。

考虑每变化一次张数会变大或变小 |a/db/d|,且要么一直变大,要么一直变小,可以发现这是一个等差数列,所以只需要将总张数最大和总张数最小的情况算出,就可以套用等差数列求和公式来求解张数。

完整代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
int extend_gcd(int a, int b, int &x, int &y){
	if(b == 0){
		x = 1;
		y = 0;
		return 0;
	}
	int d = extend_gcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int gcd(int a, int b){
	return b ? gcd(b, a % b) : a;
}
int T, opt, a, b, c;
signed main(){
	freopen("pay.in", "r", stdin);
	freopen("pay.out", "w", stdout);
	scanf("%lld%lld", &T, &opt);
	while(T--){
		scanf("%lld%lld%lld", &a, &b, &c);
		if(c % gcd(a, b) != 0){
			if(opt == 1)
				printf("0\n");
			else
				printf("0 0\n");
			continue;
		}	
		int d = gcd(a, b);
		int x, y;
		extend_gcd(a, b, x, y);
		x *= c / d;
		y *= c / d;
		int dis;
		if(x < 0){
			dis = ceil((-x) * 1.0 / (b / d));
			x += dis * (b / d);
			y -= dis * (a / d);
		} else {
			dis = floor(x * 1.0 / (b / d));
			x -= dis * (b / d);
			y += dis * (a / d);
		}
		if(y >= 0){
			printf("%lld", y / (a / d) + 1);
			if(opt == 1)
				printf("\n");
			else {
				printf(" ");
				int ans1 = x + y, ans2 = y % (a / d) + x + y / (a / d) * (b / d);
				int ans = (ans1 + ans2) * (y / (a / d) + 1) / 2;
				printf("%lld\n", ans);
			}
		} else {
			if(opt == 1)
				printf("0\n");
			else
				printf("0 0\n");
			continue;
		}
	}
	return 0;
}

T2

题目描述

Mr.Hu 被传送到了一个无限大的表格上,现在这个表格的第 i 行第 j 列的值是 ai,j(0i,j)

ai,j={1:j=0i=jai1,j+ai1,j1:0<j<i0:j>i

现在,Mr.Hu 站在 (n,m) 这个位置,他想知道,他向上或向左上方 45 度望去,看到的数的和是多少。

(n,m) 向上望去,他会看到 (n,m),(n1,m),(n2,m),,(0,m) 这些位置。

(n,m) 向左上方 45 度望去,他会看到 (n,m),(n1,m1),,直到某一维的下标变为 0

这个数可能很大,你只需将答案对 109+7 取模即可。

输入格式

1 行一个整数:T,表示数据组数。

接下来 T 行,每行格式为:dir n m,其中 dir1 表示向上看,2 表示向左上方看,(n,m)Mr.Hu 现在的位置。

输出格式

对于每组数据,输出一行表示答案。

输入样例

2
1 3 2
2 3 2

输出样例

4
6

样例解释

表格左上角长成这样(行列都是从 0 开始的):

1000110012101331

这样从 (3,2) 向上看,会看到:3,1,0,0,和为 4

向左上角看,会看到:3,2,1,和为 6

数据规模

对于 30% 数据,1n,m5000,1T1000

对于 100% 数据,1n,m106,1T50000

题解

考虑道题目生成的是杨辉三角,那么根据二项式定理可得,每一个数字就是 (nm),那么 Mr.Hu 往上看,就可以看到 i=0n(im),根据上指标求和 (参考组合数学学习笔记(一)(2024.7.3)) 可得原式 =(n+1m+1)

考虑往左上方看,可以看到的数的和为 i=0n(nimi),从组合意义思考,将式子配对,比如 (nm)(nm0) 配对,(n1m1)(nm+11) 配对,可以发现这相当于把一个序列分为两部分,两边分别做组合,如果把分界点也当成一个元素,那么原式就相当于 (n+1m)

预处理一下阶乘就做完了。

完整代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MOD = 1e9 + 7, N = 1e6 + 9;
int T, pro[N];
int extend_gcd(int a, int b, int &x, int &y){
	if(b == 0){
		x = 1;
		y = 0;
		return 0;
	}
	int d = extend_gcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int inv(int a, int m){
	int x, y;
	int d = extend_gcd(a, m, x, y);
	return (x % m + m) % m;
}
int C(int n, int m){
	int div = pro[m] * pro[n - m] % MOD;
	return pro[n] * inv(div, MOD) % MOD;
}
signed main(){
	freopen("sumcomb.in", "r", stdin);
	freopen("sumcomb.out", "w", stdout);
	pro[0] = 1;
	for(int i = 1; i <= 1000000; i++)
		pro[i] = pro[i - 1] * i % MOD;
	scanf("%lld", &T);
	while(T--){
		int d, n, m;
		scanf("%lld%lld%lld", &d, &n, &m);
		if(m > n){
			printf("0\n");
			continue;
		}
		if(d == 1)
			printf("%lld\n", C(n + 1, m + 1));
		else
			printf("%lld\n", C(n + 1, m));
	}
	return 0;
}

T3

题目描述

Mr.Hu 觉得在学习过程中,需要举一反三,做一题要理解透,然后遇到相似的问题时能类似地转化。所以想了一道和以前类似的题目,相信聪明如你,肯定能轻而易举地解决。

Mr.Hu 会给你 n 个非负整数,然后从中选 k 个出来,然后把这 k 个数按位或起来,Mr.Hu 想知道有多少种选法,使得或起来的结果为 r

输入格式

1 行一个整数 T,表示测试组数。

接下来 T 组数据,对于每组数据。

1 行两个整数 n,k,r

接下来 1 行包含 n 个非负整数:a1,a2,,an

输出格式

对于每组数据,输出一行,包含一个整数,即方案数,因为结果可能很大,只需要对 109+7 取模即可。

输入样例

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

输出样例

3
1

样例解释

对于第一组数据,一共有 3 种选法:(1,2),(1,3),(2,3)

对于第二组数据,一共有 1 种选法:(1)

数据规模

对于 10% 数据,1n10,0ai<210

对于 30% 数据,1n100,0ai<210

对于 50% 数据,1n105,0ai<215

对于 100% 数据,1n105,0ai<220,1kn,1T5

题解

以下做法来自大佬 huangkx

原题目要求的是 TS,|T|=k[OrT=r]

S 取反,原式 =TS,|T|=k[AndT=g]

考虑到 And 相当于在二进制下的 gcd,如果是 gcd 操作,那么原式就相当于 TS,|T|=k[gcdT=g]=TS,|T|=k[gcd(t1g,t2g,)=1]

运用莫比乌斯反演,可以得出原式 =dμ(d)(xS[dgx]k)

再将其转换回二进制定义,可以得出答案 =d=02201(1)popcount(d)(f(dg)k),其中 f(y)=xS[yx]

现在 popcount 和组合数可以预处理,考虑如何处理 f(dg),发现是在判断 y 是多少个 S 的一个子集的子集,可以用子集和 DP 来做。

完整代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 9, L = (1 << 20), P = 1000000007;
int sum[L], fac[N], inv[N];
int extend_gcd(int a, int b, int &x, int &y){
	if(b == 0){
		x = 1;
		y = 0;
		return 0;
	}
	int d = extend_gcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int invv(int a, int m){
	int x, y;
	int d = extend_gcd(a, m, x, y);
	return (x % m + m) % m;
}
void Init(){
	fac[0] = 1;
	for(int i = 1; i <= N; i++)
		fac[i] = fac[i - 1] * i % P;
	inv[N] = invv(fac[N], P);
	for(int i = N - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1) % P;
}
int C(int x, int y){
	if(x < y)
		return 0;
	return fac[x] * inv[y] % P * inv[x - y] % P;
}
signed main(){
	freopen("kor.in", "r", stdin);
	freopen("kor.out", "w", stdout);
	Init();
	int T;
	scanf("%lld", &T);
	while(T--){
		int n, k, r;
		scanf("%lld%lld%lld", &n, &k, &r);
		if(r >= L){
			printf("0\n");
			continue;
		}
		for(int i = 0; i < L; i++)
			sum[i] = 0;
		int g = 0;
		for(int i = 0; i < 20; i++)
			if((r & (1 << i)) == 0)
				g |= (1 << i);
		while(n--){
			int x;
			scanf("%lld", &x);
			int y = 0;
			for(int i = 0; i < 20; i++)
				if((x & (1 << i)) == 0)
					y |= (1 << i);
			sum[y]++;
		}
		for(int i = 0; i < 20; i++)
			for(int j = L - 1; j >= 0; j--)
				if((j & (1 << i)) != 0)
					sum[(j ^ (1 << i))] += sum[j];
		int ans = 0;
		for(int d = 0; d < L; d++)
			if((d & g) == 0){
				if((__builtin_popcount(d) & 1) == 1)
					ans = (ans - C(sum[(d | g)], k) % P + P) % P;
				else
					ans = (ans + C(sum[(d | g)], k) % P) % P;
			}
		printf("%lld\n", ans);
	}
	return 0;
}
posted @   JPGOJCZX  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示