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 pay.out
2 2
3 4 21
2 4 12
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 pay.out
2 1
3 4 21
2 4 12
24
Note
• 对于20% 的数据,1 a; b; c 10000,1 T 1000;
• 对于另外40% 的数据,1 a; b; c 109,其中opt = 1;
• 对于另外40% 的数据,1 a; b; c 109,其中opt = 2;
• 对于100% 的数据,1 T 105,1 opt 2。
题解
就是求ax + by = c.
要求x和y都非负.
我们通过扩展欧几里得获得一组可行解. 那么由于我们知道x变化的最小正周期是b/d, 那么(x%mod + mod) % mod就能获得最小x非负解. 此时y最大. 这样我们就可以求出x最大最小解, y最大最小解, 用x最大解-最小解之差除以x最小正周期 + 1就是方案数.
再来看求和. 我们发现x每增加b/d, y就减少a/d, 那么x每变化b/d就会使和+ b/d - a/d. 那么这就是一个方案数为项数, 首项为xmin + ymax的等差数列, O(1)求和.
虽然是签到题, 但是好久没写扩欧的我还重新推了一遍….
#include<stdio.h>
typedef long long dnt;
int T, opt;
dnt gcd(dnt a, dnt b){
return (!b) ? a : gcd(b, a % b);
}
void exgcd(dnt a, dnt b, dnt &x, dnt &y){
if(!b){
x = 1;
y = 0;
return;
}
dnt xx_1, yy_1;
exgcd(b, a % b, xx_1, yy_1);
x = yy_1, y = xx_1 - a / b * yy_1;
}
inline void gozero(){
if(opt == 1) puts("0");
else puts("0 0");
}
inline void melodyy(dnt a, dnt b, dnt c){
dnt d = gcd(a, b);
if(c % d){
gozero();
return;
}
a /= d, b /= d, c /= d;
dnt x, y;
exgcd(a, b, x, y);
x *= c, y *= c, a *= d, b *= d, c *= d;
dnt xL = b / d, xmin = (x % xL + xL) % xL;
dnt ymax = (c - xmin * a) / b;
if(ymax < 0) {gozero(); return;}
dnt yL = a / d, ymin = (y % yL + yL) % yL;
dnt xmax = (c - ymin * b) / a;
if(xmax < 0) {gozero(); return;}
dnt num = (xmax - xmin) / xL + 1;
dnt base = xmin + ymax, ch = xL - yL;
dnt sum = (base + base + ch * (num - 1)) * num / 2;
if(opt == 1) printf("%I64d\n", num);
if(opt == 2) printf("%I64d %I64d\n", num ,sum);
}
int main(){
freopen("pay.in", "r", stdin);
freopen("pay.out", "w", stdout);
scanf("%d%d", &T, &opt);
dnt a, b, c;
while(T--){
scanf("%I64d%I64d%I64d", &a, &b, &c);
melodyy(a, b, c);
}
return 0;
}
Problem 2. sumcomb
Input file: sumcomb.in
Output file: sumcomb.out
Time limit: 1 second
Memory limit: 256 MB
给你杨辉三角.
现在,Mr.Hu 站在(n;m) 这个位置,他想知道,他向上或向左上方45 度望去,看到的数的和是多少。
从(n;m) 向上望去,他会看到(n;m); (n �� 1;m); (n �� 2;m); ; (0;m) 这些位置。
从(n;m) 向左上方45 度望去,他会看到(n;m); (n �� 1;m �� 1); ,直到某一维的下标变为0.
这个数可能很大,你只需将答案对109 + 7 取模即可。
Input
第1 行一个整数:T,表示数据组数。
接下来T 行,每行格式为:dir n m,其中dir 为1 表示向上看,2 表示向左上方看,(n;m) 为Mr.Hu
现在的位置。
Output
对于每组数据,输出一行表示答案。
Sample
sumcomb.in sumcomb.out
21
3 2
2 3 2
46
表格左上角长成这样(行列都是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。
题解
大水题一道, 稍微看看就能发现操作1答案就是C(n +1, m + 1). 另外一个就是C(n +1,m).
三种发现方法:
1. 推公式. C(n , m) +C(n-1, m) …. C(m, m)实际上只要在末尾加一个C(m, m + 1)即0就可以往前不断组合成C(n +1, m + 1). 另一个同理.
2.移项: 最上方或者最左上方其实都是1, 把那个1变换位置一下, 就可以在杨辉三角上不断组合得答案.
3.打表.
#include<stdio.h>
typedef long long dnt;
const int mod = 1e9 + 7;
const int maxn = 1e6 + 5;
int T, opt;
dnt calc[maxn + 5];
inline dnt inv(dnt a){
dnt b = mod - 2;
dnt ans = 1;
while(b){
if(b & 1) ans = ans * a % mod;
a = a * a % mod, b >>= 1;
}
return ans;
}
inline dnt C(int n, int m){
dnt vv = calc[m] * calc[n - m] % mod;
return calc[n] * inv(vv) % mod;
}
int main(){
freopen("sumcomb.in", "r", stdin);
freopen("sumcomb.out", "w", stdout);
scanf("%d", &T);
calc[0] = 1;
for(int i = 1; i <= maxn; ++i) calc[i] = i * calc[i - 1] % mod;
while(T--){
int n, m;
scanf("%d%d%d", &opt, &n, &m);
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;
}
/*
2
1 3 2
2 3 2
*/
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 kor.out
24
2 3
1 2 3 4
4 1 1
1 2 3 4
31
对于第一组数据,一共有3 种选法:(1; 2); (1; 3); (2; 3)。
对于第二组数据,一共有1 种选法:(1)。
Note
• 对于10% 的数据,1 n 10,0 ai < 210;
• 对于30% 的数据,1 n 100,0 ai < 210;
• 对于50% 的数据,1 n 105,0 ai < 215;
• 对于100% 的数据,1 n 105,0 ai < 220,1 k n,1 T 5。
题解
肯定是容斥.
首先考虑如果不是或是与的话怎么做.
2.1 10%
暴力dfs 所有k 组合即可.
复杂度O(C(n; k)).
2.2 30%
考虑动态规划, 状态dp[i][j][S] 表示前i 个数选了j 个出来, 其按位与为S 的方
案数, 每次O(1) 转移.
复杂度O(n22k), 其中k 为最大位数.
2.3 60%
考虑容斥原理.
我们用cnt1[S] 表示值为S 的数的个数, 通过它可以计算出cnt2[S], 表示值”
包含”S 的数的个数(a 包含b 当且仅当a&b = b).
这一步可以先枚举一个s, 然后枚举U ��s 的子集ss, 将所有cnt1[sjss] 加到
cnt2[s] 中即可, 复杂度(3k),k 为最大位数.
然后通过cnt2[S] 可以计算出cnt3[S], 表示从原来的n 个数中选择k 个并
取交后, 该值包含S 的方案数. 显然有cnt3[s] = comb(cnt2[s]; k).
然后计算cnt4[S], 表示取k 个数取交后结果为S 的方案数. 和第二步类似,
只是每次从加变成减(从大到小枚举).
以上第一步和第三步复杂度为O(3k), 第二步为O(2k)
总的复杂度为O(3k + n)
2.4 100%
总的思想和上面一致, 只是我们可以将第一步和第三步复杂度优化到O(k2^k), 枚举子集来优化容斥, 下面有讲.
再考虑或.
或我们只需要求出cnt[S]表示能组成S的子集的方案有多少.
那么或起来刚好为S的只需要减去这个cnt[S的子集]. 但是我们会发现要删重, 那么这里就是容斥原理.
说到容斥原理的优化的话. 恩其实我的理解方法比较抽象. 所以这里抱歉的写几句方便自己以后能复习看懂的话, 如果是其他读者的话只能期望看到这几句话有所启发了.
我们需要所有cnt子集都被枚举到且只被算到一次. 其实我们只需要一位一位的传递就可以了, 把小的传递给大的, 这个看代码就知道. 但是怎么保证只贡献一次呢. 其实我们会发现i是单调递增的. 比如0000贡献给了0001, 0010, 0100, 1000(只差一位), 那么我们就要求这4个数贡献给别人的时候不会重复. 但是0010再去贡献的时候, 由于i是单增的素以永远第0位不会为1. 但是0001贡献出去的由于是或所以第0为一定有1, 这就说明了他们没有交集. 其他的类似证明. 对于容斥里的加减在这里只需要-. 因为贡献每传递一次符号就取反. -+ = -. – = +.
#include<stdio.h>
#include<cstring>
#define clear(a) memset(a, 0, sizeof(a))
typedef long long dnt;
const int P = 20;
const int mod = 1e9 + 7;
const int maxn = 1e5 + 10;
dnt calc[maxn + 5], inv[maxn + 5];
int n, k, r, T, pw[22], cnt[1 << P], a[maxn];
inline dnt mpow(dnt a){
dnt b = mod - 2;
dnt ans = 1;
while(b){
if(b & 1) ans = ans * a % mod;
a = a * a % mod, b >>= 1;
}
return ans;
}
inline dnt C(int n, int m){
if(m > n) return 0;
return calc[n] * inv[m] % mod * inv[n - m] % mod;
}
inline void fix(int &x){
if(x >= mod) x -= mod;
if(x < 0) x += mod;
}
inline void getcnt2(){
for(int i = 0; i < P; ++i){
int s = (pw[P] - 1) ^ pw[i];
for(int ss = s; ss > 0; ss = (ss - 1) & s){
cnt[ss | pw[i]] += cnt[ss];
fix(cnt[ss | pw[i]]);
}
cnt[pw[i]] += cnt[0];
fix(cnt[pw[i]]);
}
}
inline void getcnt4(){
for(int i = 0; i < P; ++i){
int s = (pw[P] - 1) ^ pw[i];
for(int ss = s; ss > 0; ss = (ss - 1) & s){
cnt[ss | pw[i]] -= cnt[ss];
fix(cnt[ss | pw[i]]);
}
cnt[pw[i]] -= cnt[0];
fix(cnt[pw[i]]);
}
}
int main(){
freopen("kor.in", "r", stdin);
freopen("kor.out", "w", stdout);
scanf("%d", &T);
pw[0] = 1, calc[0] = 1;
for(int i = 1; i <= P; ++i) pw[i] = pw[i - 1] << 1;
for(int i = 1; i <= maxn; ++i) calc[i] = i * calc[i - 1] % mod;
inv[maxn] = mpow(calc[maxn]);
for(int i = maxn - 1; ~i; --i)
inv[i] = inv[i + 1] * (i + 1) % mod;
while(T--){
clear(cnt);
scanf("%d%d%d", &n, &k, &r);
for(int i = 1; i <= n; ++i){
scanf("%d", &a[i]);
cnt[a[i]]++;
}
getcnt2();
for(int i = 0; i < pw[P]; ++i)
cnt[i] = C(cnt[i], k);
getcnt4();
printf("%d\n", cnt[r]);
}
}
/*
2
4 2 3
1 2 3 4
4 1 1
1 2 3 4
*/