CSP-S 考前数学练习
[HAOI2011] 向量
首先将题目转化,转化为求方程:
将这个方程再次化简,即为:
到这里,我们可以联想到 定理, 定理为 , 和 有整数解的充要条件是
所以,为了使 都为整数的的充要条件是 和 的最大公因数都可以整除 ,即为 且
考虑分类讨论:
1.当 都是偶数时:
在 且 的基础上可以再提取一波公因数 , 所以此时为 且
2.当 都是奇数时:
两边同时加上 ,可得
此时 且
3.当 是偶数, 都是奇数时:
两边同时加上 ,可得
此时 ,同理,
4.当 是奇数, 都是偶数时:
两边同时加上 ,可得
此时 ,同理,
所以我们只需要 check 一下该组数据是否符合上面任一一种情况就可以了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#define rint register int
#define endl '\n'
#define int long long
int k;
bool f(int a, int b)
{
return (!(a % k)) and (!(b % k));
}
signed main()
{
int T;
scanf("%lld", &T);
while (T--)
{
int a, b, x, y;
scanf("%lld%lld%lld%lld", &a, &b, &x, &y);
k = std::__gcd(a, b) * 2;
if (f(x, y) || f(x + a, y + b) || f(x + b, y + a) || f(x + a + b, y + a + b))
{
puts("Y");
}
else
{
puts("N");
}
}
return 0;
}
UVA12775 Gift Dilemma
求方程 非负整数解的个数。
设
显然,方程成立一定会有
则
设
所以
可以扩展欧几里得定理求出 的一组整数特解
将两个式子同时除以 再乘 ,可以得到:
得到原方程的整数特解
因为
所以
这一步怎么来的呢?因为
给定 变化的倍数,就是得到了通解的形式,其中 为任意整数。
因为要求的是非负整数解,所以
我们接着进行转换:
即为
所以
原方程的非负整数解个数即为:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define rint register int
#define endl '\n'
#define int long long
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int z = x;
x = y;
y = z - y * (a / b);
return d;
}
signed main()
{
int T;
scanf("%lld", &T);
int idx = 0;
while (T--)
{
idx++;
int A, B, C, P;
scanf("%lld%lld%lld%lld", &A, &B, &C, &P);
int x, y;
int ans = 0;
int d = exgcd(exgcd(A, B, x, y), C, x, y);
if (P % d)
{
printf("Case %lld: 0\n", idx);
continue;
}
int w = exgcd(A / d, B / d, x, y);
int x0, y0;
int x1, y1;
x0 = x;
y0 = y;
rint z = 0;
while (1)
{
if (P - z * C < 0)
{
break;
}
int c = P / d - C / d * z;
if (c % w)
{
z++;
continue;
}
x1 = x0 * c / w;
y1 = y0 * c / w;
ans += floor(x1 * d * 1.0 * w * 1.0 / B) - ceil(-y1 * d * 1.0 * w * 1.0 / A) + 1;
z++;
}
printf("Case %lld: %lld\n", idx, ans);
}
}
[SDOI2010] 古代猪文
题面很长,化简一下就是求
我们发现 是个质数,不难想到欧拉定理,可以得到 。对于底数 和最终取模数 可以直接用费马小定理求出。
所以现在需要解决的问题就是,如何求出
这里需要用到中国剩余定理。
先对 进行质因数分解
将 分别对 的四个质因数取模,构建同余方程组,求出在模通解 的通解 ,那么
最终答案即为:
然后接着可以用Lucas定理求解,就做完了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define rint register int
#define endl '\n'
#define int long long
const int Mod = 999911659;
const int mod = 999911658;
const int N = 4e4 + 5;
int n, g;
int d[N], tot;
int p[10], cnt;
int fac[N], inv[N];
int a[10];
int qpow(int a, int b, int p)
{
int res = 1;
for (; b; b >>= 1)
{
if (b & 1)
{
res = (res * a) % p;
}
a = (a * a) % p;
}
return res;
}
void init(int p)
{
fac[0] = inv[0] = 1;
for (rint i = 1; i < p; i++)
{
fac[i] = fac[i - 1] * i % p;
inv[i] = qpow(fac[i], p - 2, p);
}
}
int C(int n, int m, int p)
{
if (m > n)
{
return 0;
}
return (inv[m] * inv[n - m] % p) * fac[n] % p;
}
int lucas(int n, int m, int p)
{
if (!m)
{
return 1;
}
return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
int CRT()
{
int ans = 0;
for (rint i = 1; i <= cnt; i++)
{
int M = mod / p[i];
int t = qpow(M, p[i] - 2, p[i]);
//关于这个为什么求出来的是方程的解到现在也不太懂,之前都是用的exgcd
ans = (ans + t * M * a[i] % mod) % mod;
}
return ans;
}
void calc(int x)
{
init(p[x]);
for (rint i = 1; i <= tot; i++)
{
a[x] = (a[x] + lucas(n, d[i], p[x])) % p[x];
}
}
signed main()
{
scanf("%lld%lld", &n, &g);
if (g % Mod == 0)
{
puts("0");
return 0;
}
int t = mod;
for (rint i = 2; i <= sqrt(mod); i++)
{
if (!(t % i))
{
p[++cnt] = i;
while (t % i == 0)
{
t /= i;
}
}
}
if (t != 1)
{
p[++cnt] = t;
}
for (rint i = 1; i <= sqrt(n); i++)
{
if (!(n % i))
{
d[++tot] = i;
if (i * i != n)
{
d[++tot] = n / i;
}
}
}
for (rint i = 1; i <= cnt; i++)
{
calc(i);
}
printf("%lld", qpow(g, CRT(), Mod));
return 0;
}
[HNOI2002] 跳蚤
设题目中的那个长度为 的序列为 ,这个题就是在求使 有解的 有多少种。
由 定理,可得方程的有解情况就是
但是直接去找方程有解的情况很麻烦且很难,可以反向考虑,求出上面那个方程无解的情况。
无解时,
先把 分解质因数, 都一定有一个共同的 ,就能算出使它无解的方案数了。
但是求无解显然有重复的情况,这个时候可以容斥原理,当 都为 的倍数时,此时方案数就是
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define rint register int
#define endl '\n'
#define int long long
const int N = 1e2 + 5;
int n, m, cnt;
int a[N];
int ans;
signed main()
{
scanf("%lld%lld", &n, &m);
ans = pow(m, n);
int k = m;
for (rint i = 2; i <= sqrt(m); i++)
{
if (m % i == 0)
{
a[++cnt] = i;
while (m % i == 0)
{
m /= i;
}
}
}
if (m > 1)
{
a[++cnt] = m;
}
for (rint i = 1; i < 1 << cnt; i++)
{
int val = 1, num = 0;
for (rint j = 1; j <= cnt; j++)
{
if (i & (1 << (j - 1)))
{
num++;
val *= a[j];
}
}
if (num % 2)
{
ans -= pow(k / val, n);
}
else
{
ans += pow(k / val, n);
}
}
printf("%lld\n", ans);
return 0;
}
「KDOI-02」 一个仇的复
不难发现,肯定是由一堆横着的和一些竖着 的构成的,而竖着的将将原来的网格分成了若干段。
只有一行时,设长度为 ,用了 块。答案就是 。拓展到两个并列的一行(即两行),答案是 。
枚举有 个竖着的,其中有 段。
先考虑剩下的分成 段的方案。要求方案数,首先我们要知道能插的个数。这时需要分类讨论。
若竖着没有在开头和结尾的,那么能插的个数为 ,还要划分的段数是 ,那么方案就是
若竖着只有一个在开头结尾,那么能插的个数为 ,还要划分的段数是 ,那么方案就是
若竖着的都在开头和结尾,那么能插的个数为 ,还要划分的段数是 ,那么方案就是
接着考虑竖着的方案,发现这个直接用插板法很难求,那么考虑分步来求。先考虑划分为 段,这个直接插板求出,方案是 ,再考虑把这 段再插到原来的网格中,可以插在剩下位置的前面,但是再第一个的前面就不行,所以方案是 ,下面取决于上面的分裂讨论,那么总的方案就是乘积。
考虑枚举有 个竖着的,其中有 段的方案就是三种情况的和乘上竖着的方案。
由于此题数据较大,卡不过去,所以只写了个 70pts 的代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#define rint register int
#define endl '\n'
#define int long long
using namespace std;
const int N = 4e7 + 5;
const int M = 5e3 + 5;
const int mod = 998244353;
int n, m, inv[N], fac[N];
int qpow(int a, int b)
{
int res = 1;
for (;b ;b >>= 1)
{
if (b & 1)
res = res * a % mod;
a = a * a % mod;
}
return res;
}
void init()
{
fac[0] = inv[0] = 1;
for (rint i = 1; i <= 10000000; i++)
{
fac[i] = fac[i - 1] * i % mod;
inv[i] = qpow(fac[i], mod - 2);
}
}
int C(int n, int m)
{
return (inv[m] * inv[n - m] % mod) * fac[n] % mod;
}
signed main()
{
scanf("%lld%lld", &n, &m);
init();
int ans = C(2 * (n - 1), m - 2);
for (rint i = 1; i <= m; i++)
{
for (rint j = 1; j <= i; j++)
{
int tmp = j + 1;
if (tmp * 2 + i <= m)
{
int now1 = n - 1 - i - j;
int now2 = m - (tmp * 2 + i);
int res = C(i - 1, j - 1) * C(n - i - 1, j) % mod * C(2 * now1, now2) % mod;
ans = (ans + res) % mod;
}
tmp = j;
if (tmp * 2 + i <= m)
{
int now1 = n - i - j;
int now2 = m - (tmp * 2 + i);
int res = 2 * C(i - 1, j - 1) * C(n - i - 1, j - 1)%mod * C(2 * now1, now2)%mod;
ans = (ans + res) % mod;
}
tmp = j - 1;
if (tmp * 2 + i <= m && j >= 2)
{
int now1 = n + 1 - i - j;
int now2 = m - (tmp * 2 + i);
int res = C(i - 1, j - 1) * C(n - i - 1, j - 2) % mod * C(2 * now1, now2) % mod;
ans = (ans + res) % mod;
}
}
}
printf("%lld\n", ans + (n == m));
return 0;
}
本文作者:PassName
本文链接:https://www.cnblogs.com/spaceswalker/p/16832664.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步