2024.9.27校测

T1

题目描述

\(Mr.Hu\) 开了个饭店,来了两位客人:\(Alice\)\(Bob\),他们吃完饭要结账时,发现他们需要支付 \(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\),所以 \(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\)

输入样例2

2 1
3 4 21
2 4 12

输出样例2

2
4

数据规模

对于 \(20\%\) 数据,\(1 \leq a, b, c \leq 10000, 1 \leq T \leq 1000\)

对于另外 \(40\%\) 数据,\(1 \leq a, b, c \leq 10^9\),其中 \(opt = 1\)

对于另外 \(40\%\) 数据,\(1 \leq a, b, c \leq 10^9\),其中 \(opt = 2\)

对于 \(100\%\) 数据,\(1 \leq T \leq 10^5, 1 \leq opt \leq 2\)

题解

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

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

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

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

考虑每变化一次张数会变大或变小 \(| a / d - b / 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\) 列的值是 \(a_{i,j}(0 \leq i, j)\)

\[a_{i,j} = \begin{cases} 1 & : j = 0 或 i = j\\ a_{i - 1, j} + a_{i - 1, j - 1} & : 0 < j < i\\ 0 & : j > i \end{cases} \]

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

\((n, m)\) 向上望去,他会看到 \((n, m), (n − 1, m), (n − 2, m), \dots,(0, m)\) 这些位置。

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

这个数可能很大,你只需将答案对 \(10^9 + 7\) 取模即可。

输入格式

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

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

输出格式

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

输入样例

2
1 3 2
2 3 2

输出样例

4
6

样例解释

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

\[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\)

数据规模

对于 \(30\%\) 数据,\(1 \leq n, m \leq 5000, 1 \leq T \leq 1000\)

对于 \(100\%\) 数据,\(1 \leq n, m \leq 10^6, 1 \leq T \leq 50000\)

题解

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

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

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

完整代码

#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\) 个非负整数:\(a_1, a_2, \dots, a_n\)

输出格式

对于每组数据,输出一行,包含一个整数,即方案数,因为结果可能很大,只需要对 \(10^9 + 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\%\) 数据,\(1 \leq n \leq 10, 0 \leq a_i < 2^{10}\)

对于 \(30\%\) 数据,\(1 \leq n \leq 100, 0 \leq a_i < 2^{10}\)

对于 \(50\%\) 数据,\(1 \leq n \leq 10^5, 0 \leq a_i < 2^{15}\)

对于 \(100\%\) 数据,\(1 \leq n \leq 10^5, 0 \leq a_i < 2^{20}, 1 \leq k \leq n, 1 \leq T \leq 5\)

题解

以下做法来自大佬 huangkx

原题目要求的是 \(\displaystyle\sum_{T \subseteq S, | T | = k}[\operatorname{Or } T = r]\)

\(S\) 取反,原式 \(= \displaystyle\sum_{T \subseteq S', | T | = k}[\operatorname{And } T = g]\)

考虑到 \(\operatorname{And}\) 相当于在二进制下的 \(\gcd\),如果是 \(\gcd\) 操作,那么原式就相当于 \(\displaystyle\sum_{T \subseteq S', | T | = k}[\gcd T = g] = \sum_{T \subseteq S', | T | = k}[\gcd(\frac{t_1}{g}, \frac{t_2}{g}, \dots) = 1]\)

运用莫比乌斯反演,可以得出原式 \(= \displaystyle\sum_d \mu(d) \binom{\sum_{x \in S'} [dg \mid x]}{k}\)

再将其转换回二进制定义,可以得出答案 \(= \displaystyle\sum_{d=0}^{2^{20} - 1} (-1)^{\operatorname{popcount}(d)}\binom{f(dg)}{k}\),其中 \(f(y) = \displaystyle\sum_{x \in S'}[y \mid x]\)

现在 \(\operatorname{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 @ 2024-09-27 20:27  JPGOJCZX  阅读(19)  评论(0)    收藏  举报