题解
D - Beautiful Numbers
题意
给定两个0-9的整数
a
a
a和
b
b
b,定义good number:数位中仅含有
a
a
a和
b
b
b的正整数。定义excellent number:数位和是good number的正整数。
先给定
n
n
n,求
n
n
n位excellent number的数量%
(
1
0
9
+
7
)
(10^9+7)
(109+7)。
1
≤
n
≤
1
0
6
1\leq n\leq 10^6
1≤n≤106
思路
数位和仅跟 a a a和 b b b的数量有关,因此可以枚举 a a a的个数 i i i,则 b b b有 n − i n-i n−i个,数位和即是 a ∗ i + b ∗ ( n − i ) a*i+b*(n-i) a∗i+b∗(n−i),判断了数位和是否为good number后可用组合数计算出有 i i i个 a a a时的数有多少个,即 C n i C_n^i Cni。最终答案为 ∑ i = 0 n C n i [ i s g o o d n u m ( a ∗ i + b ∗ ( n − i ) ) ] \sum\limits_{i=0}^nC_n^i[isgoodnum(a*i+b*(n-i))] i=0∑nCni[isgoodnum(a∗i+b∗(n−i))]。
代码
#include <bits/stdc++.h>
#define fo(i, x, y) for (int i = x; i <= (y); ++i)
#define fd(i, x, y) for (int i = x; i >= (y); --i)
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5, mod = 1e9 + 7;
ll a, b, n;
ll fac[maxn], facr[maxn];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && ch != '-');
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = res * 10 + ch - '0', ch = getchar();
return res * p;
}
ll C(ll n, ll m)
{
return fac[n] * facr[n - m] % mod * facr[m] % mod;
}
ll pw(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1) (res *= a) %= mod;
(a *= a) %= mod;
b >>= 1;
}
return res;
}
ll rev(ll x)
{
return pw(x, mod - 2);
}
bool check(ll x)
{
while (x)
{
if (x % 10 != a && x % 10 != b) return false;
x /= 10;
}
return true;
}
int main()
{
fac[0] = 1;
fo(i, 1, 1e6) fac[i] = fac[i - 1] * i % mod;
facr[0] = 1;
fo(i, 1, 1e6) facr[i] = facr[i - 1] * rev(i) % mod;
cin >> a >> b >> n;
ll ans = 0;
fo(i, 0, n)
{
if (check(a * i + b * (n - i)))
(ans += C(n, i)) %= mod;
}
cout << ans << endl;
//cerr << C(20, 7);
return 0;
}
F - 青蛙的约会
题意
求满足 x + k m ≡ y + k n ( m o d l ) x+km\equiv y+kn(\mod l) x+km≡y+kn(modl),的最小非负正整数 k k k。
思路
相当于求解
x
+
k
m
+
t
l
=
y
+
k
n
x+km+tl=y+kn
x+km+tl=y+kn,化为
a
x
+
b
y
=
c
ax+by=c
ax+by=c的形式
(
m
−
n
)
k
+
l
t
=
y
−
x
(m-n)k+lt=y-x
(m−n)k+lt=y−x,用exgcd求解即可。
关于
a
x
+
b
y
=
c
ax+by=c
ax+by=c的
x
x
x最小非负整数解的求法,
令
d
=
g
c
d
(
a
,
b
)
d=gcd(a,b)
d=gcd(a,b)
先用exgcd求出满足
a
x
0
+
b
y
0
=
d
ax_0+by_0=d
ax0+by0=d的
(
x
0
,
y
0
)
(x_0,y_0)
(x0,y0)
则
x
1
=
c
d
x
0
,
y
1
=
c
d
y
0
x_1=\frac{c}{d}x_0 ,y_1=\frac{c}{d}y_0
x1=dcx0,y1=dcy0为
a
x
+
b
y
=
c
ax+by=c
ax+by=c的一个解,
通解为
(
x
1
+
k
b
g
,
y
1
−
k
a
g
)
(x_1+k\frac{b}{g},y_1-k\frac{a}{g})
(x1+kgb,y1−kga),
∣
b
g
∣
|\frac{b}{g}|
∣gb∣为
x
x
x的最小正周期
令
t
x
=
∣
b
g
∣
t_x=|\frac{b}{g}|
tx=∣gb∣,则
x
x
x的最小非负整数解为
(
x
1
%
t
x
+
t
x
)
%
t
x
(x_1\%t_x+t_x)\%t_x
(x1%tx+tx)%tx;
还有一种做法是直接令
x
1
+
k
b
g
≥
0
x_1+k\frac{b}{g}\geq0
x1+kgb≥0解出
k
k
k的取值,然后取
k
k
k的边界值。
代码
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <iostream>
#define fo(i, x, y) for (int i = x; i <= (y); ++i)
#define fd(i, x, y) for (int i = x; i >= (y); --i)
using namespace std;
typedef long long ll;
ll x, y, m, n, l;
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && ch != '-');
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = res * 10 + ch - '0', ch = getchar();
return res * p;
}
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;
}
int main()
{
cin >> x >> y >> m >> n >> l;
ll a = ((m - n) % l + l) % l, b = l, c = ((y - x) % l + l) % l;
ll d, k0, t0;
d = exgcd(a, b, k0, t0);
if (c % d) {printf("Impossible\n"); return 0;}
ll aa = a / d, bb = b / d, cc = c / d;
ll r = ceil((1.0 - cc * k0) / bb);
printf("%lld\n", cc * k0 + bb * r);
return 0;
}
I - Saving Beans
题意
把不超过
m
m
m个相同的球放进
n
n
n个不同的盒子,盒子可以为空,求方案数%p。
1
≤
n
,
m
≤
1
0
9
,
1
<
p
<
1
0
5
1 \leq n, m \leq 10^9, 1 < p < 10^5
1≤n,m≤109,1<p<105
思路
经典的小球隔板问题,先考虑有
m
m
m个球的情况,在每个盒子多放一个球,则可以转化为求有
m
+
n
m+n
m+n个球放
n
−
1
n-1
n−1个隔板且每个盒子不为空的方案数,答案即是
C
m
+
n
−
1
n
−
1
C_{m+n-1}^{n-1}
Cm+n−1n−1,球的取值范围是
[
0
,
m
]
[0,m]
[0,m]所以答案为
∑
k
=
0
m
C
k
+
n
−
1
n
−
1
=
∑
k
=
0
m
C
k
+
n
−
1
k
=
1
+
C
1
+
n
−
1
1
+
C
2
+
n
−
1
2
⋯
+
C
m
+
n
−
1
m
\sum\limits_{k=0}^mC_{k+n-1}^{n-1}=\sum\limits_{k=0}^mC_{k+n-1}^{k}=1+C_{1+n-1}^1+C_{2+n-1}^2\dots+C_{m+n-1}^m
k=0∑mCk+n−1n−1=k=0∑mCk+n−1k=1+C1+n−11+C2+n−12⋯+Cm+n−1m,把1替换成
C
1
+
n
−
1
0
C_{1+n-1}^0
C1+n−10,前两项变成
C
2
+
n
−
1
1
C_{2+n-1}^1
C2+n−11,不断取前两项结合最终可化简为
C
m
+
n
m
C_{m+n}^m
Cm+nm。
还有一种更简单的推法,可以看成有
n
+
1
n+1
n+1个盒子,多出来的盒子用于放多出来的球,直接用隔板法推出答案
C
m
+
n
n
C_{m+n}^n
Cm+nn。
但是
n
,
m
n,m
n,m很大,无法直接求解组合数,观察到
p
p
p不超过
1
0
5
10^5
105,可用lucas定理,
C
n
m
=
C
n
m
o
d
p
m
m
o
d
p
∗
C
n
/
p
m
/
p
(
m
o
d
p
)
C_n^m=C_{n\mod p}^{m\mod p}*C_{n/p}^{m/p}(\mod p)
Cnm=Cnmodpmmodp∗Cn/pm/p(modp)
代码
#include <bits/stdc++.h>
#define fo(i, x, y) for (int i = x; i <= (y); ++i)
#define fd(i, x, y) for (int i = x; i >= (y); --i)
using namespace std;
typedef long long ll;
const int maxp = 1e5 + 5;
ll n, m, p;
ll fac[maxp], facr[maxp];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && ch != '-');
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = res * 10 + ch - '0', ch = getchar();
return res * p;
}
ll pw(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1) (res *= a) %= p;
(a *= a) %= p;
b >>= 1;
}
return res;
}
ll rev(ll x)
{
return pw(x, p - 2);
}
ll C(ll n, ll m)
{
return n >= m? fac[n] * rev(fac[n - m] * fac[m] % p) % p : 0;
}
ll lucas(ll n, ll m)
{
if (n < m) return 0;
if (!m) return 1;
if (n >= p || m >= p) return C(n % p, m % p) * lucas(n / p, m / p) % p;
return C(n, m);
}
void work()
{
cin >> n >> m >> p;
fac[0] = 1;
fo(i, 1, p - 1) fac[i] = fac[i - 1] * i % p;
cout << lucas(n + m, n) << '\n';
}
int main()
{
int T;
T = getint();
while (T--) work();
return 0;
}
J - Sum
题意
给定 n n n,设 s ( k ) s(k) s(k)为满足一下条件的 ( x 1 , x 2 , ⋯ , x k ) (x_1,x_2,\cdots,x_k) (x1,x2,⋯,xk),
- x 1 , x 2 , ⋯ , x k ∈ Z + x_1,x_2,\cdots,x_k\in\mathbb{Z^+} x1,x2,⋯,xk∈Z+
- ∑ i = 1 k x i = n \sum\limits_{i=1}^kx_i=n i=1∑kxi=n
求
(
∑
i
=
1
n
s
(
i
)
)
m
o
d
(
1
e
9
+
7
)
(\sum\limits_{i=1}^ns(i))\mod(1e9+7)
(i=1∑ns(i))mod(1e9+7)
1
≤
n
≤
1
0
1
0
5
1\leq n\leq10^{10^5}
1≤n≤10105
思路
用小球隔板模型可得
s
(
k
)
=
C
n
−
1
k
−
1
s(k)=C_{n-1}^{k-1}
s(k)=Cn−1k−1
则
∑
i
=
1
n
s
(
i
)
=
∑
i
=
1
n
C
n
−
1
k
−
1
=
2
n
−
1
\sum\limits_{i=1}^ns(i)=\sum\limits_{i=1}^{n}C_{n-1}^{k-1}=2^{n-1}
i=1∑ns(i)=i=1∑nCn−1k−1=2n−1
现在有一个问题,
n
n
n很大,无法直接求
2
n
−
1
2^{n-1}
2n−1
这里就要用到欧拉降幂,以下
φ
(
x
)
\varphi(x)
φ(x)为欧拉函数
欧拉定理:
a
φ
(
n
)
≡
1
(
m
o
d
n
)
a^{\varphi(n)}\equiv1(\mod n)
aφ(n)≡1(modn)
欧拉定理推论:
a
b
≡
a
b
m
o
d
φ
(
n
)
(
m
o
d
n
)
a^b\equiv a^{b\mod\varphi(n)}(\mod n)
ab≡abmodφ(n)(modn)
当
p
p
p为质数时
φ
(
p
)
=
p
−
1
\varphi(p)=p-1
φ(p)=p−1,所以此题答案为
2
n
m
o
d
(
1
0
9
+
7
−
1
)
2^{n\mod(10^9+7-1)}
2nmod(109+7−1)。
代码
#include <bits/stdc++.h>
#define fo(i, x, y) for (int i = x; i <= (y); ++i)
#define fd(i, x, y) for (int i = x; i >= (y); --i)
using namespace std;
typedef long long ll;
const int maxl = 1e5 + 5, mod = 1e9 + 7;
char str[maxl];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && ch != '-');
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = res * 10 + ch - '0', ch = getchar();
return res * p;
}
ll pw(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1) (res *= a) %= mod;
(a *= a) %= mod;
b >>= 1;
}
return res;
}
int main()
{
while (~scanf("%s", str + 1))
{
int len = strlen(str + 1);
ll k = 0;
fo(i, 1, len)
k = (k * 10 + str[i] - '0') % (mod - 1);
k = (k - 1 + mod - 1) % (mod - 1);
cout << pw(2, k) << endl;
}
return 0;
}
知识总结
exgcd:用于解形如
a
x
+
b
y
=
c
ax+by=c
ax+by=c的不定方程,列好式子后用模板和通解公式即可。
组合计数:小球隔板模型及其变形是比较重要的模型,已经在一些比赛中遇到过这样的题,难点在于如何把题目的问题转化为小球隔板模型。还有一些组合数比较常用的性质:
- C n m = C n n − m C_n^m=C_n^{n-m} Cnm=Cnn−m
- C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1
- ∑ i = 1 n C n i = 2 n \sum\limits_{i=1}^nC_n^i=2^n i=1∑nCni=2n
逆元:直接用费马小定理,如果模数不是质数用exgcd求。
lucas:用于求解
n
,
m
n,m
n,m比较大但模数比较小的组合数。
欧拉降幂:遇到指数比较大的幂次方求解时,用此定理可把指数变小。
接下来剩下的数论知识还要再复习、学习,还要多刷题提高熟练度。