CSP-S 考前数学练习

[HAOI2011] 向量

首先将题目转化,转化为求方程:

\(k(a,b)+q(b,a)+w(a,−b)+c(b,−a)=(x,y)\)

将这个方程再次化简,即为:

\((k+w)a+(q+c)b=x\)

\((k-w)b+(q-c)a=y\)

到这里,我们可以联想到 \(Bézout\) 定理,\(Bézout\) 定理为 \(ax+by=c\) , \(x\)\(y\) 有整数解的充要条件是 \(gcd(a,b)∣c\)

所以,为了使 \(k+w,q+c,k-w,q-c\) 都为整数的的充要条件是 \(a\)\(b\) 的最大公因数都可以整除 \(x, y\),即为 \(gcd(a,b)∣x\)\(gcd(a,b)∣y\)

考虑分类讨论:

1.当 \(k+w,q+c,k-w,q-c\) 都是偶数时:

\(gcd(a,b)∣x\)\(gcd(a,b)∣y\) 的基础上可以再提取一波公因数 \(2\), 所以此时为 \(2gcd(a,b)∣x\)\(2gcd(a,b)∣y\)

2.当 \(k+w,q+c,k-w,q-c\) 都是奇数时:

\((k+w)a+(q+c)b=x\) 两边同时加上 \(a+b\),可得 \((k+w+1)a+(q+c+1)b=x+a+b\)

此时 \(2gcd(a,b)∣x + a + b\)\(2gcd(a,b)∣y +a + b\)

3.当 \(k+w,k-w\) 是偶数,\(q+c,q-c\) 都是奇数时:

\((k+w)a+(q+c)b=x\) 两边同时加上 \(b\),可得 \((k+w)a+(q+c+1)b=x+b\)

此时 \(2gcd(a,b)∣x + b\),同理, \(2gcd(a,b)∣y + a\)

4.当 \(k+w,k-w\) 是奇数,\(q+c,q-c\) 都是偶数时:

\((k+w)a+(q+c)b=x\) 两边同时加上 \(a\),可得 \((k+w+1)a+(q+c)b=x+a\)

此时 \(2gcd(a,b)∣x + a\),同理, \(2gcd(a,b)∣y + b\)

所以我们只需要 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

求方程 \(Ax+By+Cz=P\space \space \space \space \space \space (x\ge0,y\ge0,z\ge0)\) 非负整数解的个数。

\(d=\gcd(A,B,C)\)

\(d\times(\frac{A}{d}x+\frac{B}{d}y+\frac{C}{d}z)=P\)

显然,方程成立一定会有 \(d|P\)

\(\frac{A}{d}x+\frac{B}{d}y+\frac{C}{d}z=\frac{P}{d}\)

\(\frac{A}{d}x+\frac{B}{d}y=\frac{P}{d}-\frac{C}{d}z\)

\(c=\frac{P}{d}-\frac{C}{d}z\)

所以 \(\frac{A}{d}x+\frac{B}{d}y=c\)

可以扩展欧几里得定理求出 \(\frac{A}{d}x+\frac{B}{d}y=\gcd(\frac{A}{d},\frac{B}{d})\) 的一组整数特解 \(x_0,y_0\)

\(\frac{A}{d}x_0+\frac{B}{d}y_0=\gcd(\frac{A}{d},\frac{B}{d})\)

将两个式子同时除以 \(\gcd(\frac{A}{d},\frac{B}{d})\) 再乘 \(c\) ,可以得到:

\(\frac{A}{d}\times\frac{x_0c}{\gcd(\frac{A}{d},\frac{B}{d})}+\frac{B}{d}\times\frac{y_0c}{\gcd(\frac{A}{d},\frac{B}{d})}=c\)

得到原方程的整数特解

\(x_1=\frac{x_0c}{\gcd(\frac{A}{d},\frac{B}{d})},y_1=\frac{y_0c}{\gcd(\frac{A}{d},\frac{B}{d})}\)

因为 \(\frac{A}{d}\times x_1+\frac{B}{d}\times y_1=c\)

所以 \(\frac{A}{d}\times (x_1- \frac{B}{d\times\gcd(\frac{A}{d},\frac{B}{d})})+\frac{B}{d}\times (y_1+\frac{A}{d\times\gcd(\frac{A}{d},\frac{B}{d})})=c\)

这一步怎么来的呢?因为 \((- \frac{B}{d\times\gcd(\frac{A}{d},\frac{B}{d})} \times \frac{A}{d}) + (\frac{A}{d\times\gcd(\frac{A}{d},\frac{B}{d})} \times \frac{B}{d}) = 0\)

给定 \(x_1,y_1\) 变化的倍数,就是得到了通解的形式,其中 \(s\) 为任意整数。

\(\frac{A}{d}\times (x_1- s\times\frac{B}{d\times\gcd(\frac{A}{d},\frac{B}{d})})+\frac{B}{d}\times (y_1+s\times\frac{A}{d\times\gcd(\frac{A}{d},\frac{B}{d})})=c\)

因为要求的是非负整数解,所以

\(x_1- s\times\frac{B}{d\times\gcd(\frac{A}{d},\frac{B}{d})}\ge0\)

\(y_1+s\times\frac{A}{d\times\gcd(\frac{A}{d},\frac{B}{d})}\ge0\)

我们接着进行转换:

\(x_1\ge s\times\frac{B}{d\times\gcd(\frac{A}{d},\frac{B}{d})}\)

\(y_1\ge -s\times\frac{A}{d\times\gcd(\frac{A}{d},\frac{B}{d})}\)

即为

\(x_1\times\frac{d\times\gcd(\frac{A}{d},\frac{B}{d})}{B}\ge s\)

\(-y_1\times\frac{d\times\gcd(\frac{A}{d},\frac{B}{d})}{A}\le s\)

所以

\(-y_1\times\frac{d\times\gcd(\frac{A}{d},\frac{B}{d})}{A}\le s\le x_1\times\frac{d\times\gcd(\frac{A}{d},\frac{B}{d})}{B}\)

原方程的非负整数解个数即为:\(\lfloor x_1\times\frac{d\times\gcd(\frac{A}{d},\frac{B}{d})}{B}\rfloor-\lceil-y_1\times\frac{d\times\gcd(\frac{A}{d},\frac{B}{d})}{A}\rceil+1\)

#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] 古代猪文

题面很长,化简一下就是求 \(G^{\sum_{d|n}{C^{d}_{n}}}mod\ 999911659\)

我们发现 \(999911659\) 是个质数,不难想到欧拉定理,可以得到 \(G^{\sum_{d|n}{C^{d}_{n}}}\equiv\ G^{\sum_{d|n}C^{d}_{n}\ mod\ 999911658}\ (mod\ 999911659)\)。对于底数 \(G\) 和最终取模数 $mod $ \(999911659\) 可以直接用费马小定理求出。

所以现在需要解决的问题就是,如何求出 \(\sum_{d|n}{C^{d}_{n}\ mod\ 999911658}\)

这里需要用到中国剩余定理

先对 \(999911658\) 进行质因数分解 \(999911658=2\times3\times4697\times35617\)

\(\sum_{d|n}C^{d}_{n}\) 分别对 \(999911658\) 的四个质因数取模,构建同余方程组,求出在模通解 \(999911658\) 的通解 \(t\) ,那么 $ t=\sum_{d|n}C^{d}_{n}mod\ 999911658$

最终答案即为:\(G^{t}\ mod\) \(999911659\)

\(\begin{cases} x\equiv \sum_{d|n}C^{d}_{n}(mod\ 2)\\ x\equiv \sum_{d|n}C^{d}_{n}(mod\ 3)\\ x\equiv \sum_{d|n}C^{d}_{n}(mod\ 4679)\\ x\equiv \sum_{d|n}C^{d}_{n}(mod\ 35617)\\ \end{cases}\)

然后接着可以用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] 跳蚤

设题目中的那个长度为 \(n+1\) 的序列为 \(\{a_1...a_n,m\}\),这个题就是在求使 \(\sum_{i=1}^{n}a_i\times x_i+m\times x_{n+1}=1\) 有解的 \(a_1,a_2...a_n\) 有多少种。

\(Bézout\) 定理,可得方程的有解情况就是 \(\gcd(a_1,a_2...a_n,m)=1\)

但是直接去找方程有解的情况很麻烦且很难,可以反向考虑,求出上面那个方程无解的情况。

无解时,\(\gcd(a_1,a_2...,a_n,m)\not=1\)

先把 \(m\) 分解质因数,\(a_1...a_n\) 都一定有一个共同的 \(n\),就能算出使它无解的方案数了。

但是求无解显然有重复的情况,这个时候可以容斥原理,当 \(a_i\) 都为 \(x\) 的倍数时,此时方案数就是 $(\frac{m}{x})^{n} $

#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」 一个仇的复

不难发现,肯定是由一堆横着的和一些竖着 \(2 \times 1\) 的构成的,而竖着的将将原来的网格分成了若干段。

只有一行时,设长度为 \(n\),用了 \(m\) 块。答案就是 \(\dbinom {n-1} {m-1}\)。拓展到两个并列的一行(即两行),答案是 \(\dbinom {2n-2} {m-1}\)

枚举有 \(i\) 个竖着的,其中有 \(j\) 段。

先考虑剩下的分成 \(n-i\) 段的方案。要求方案数,首先我们要知道能插的个数。这时需要分类讨论。

若竖着没有在开头和结尾的,那么能插的个数为 \(2(n-1-i-j)\),还要划分的段数是 \(m-2(j+1)-i\),那么方案就是 \(\dbinom {2(n-1-i-j)} {m-2(j+1)-i}\)

若竖着只有一个在开头结尾,那么能插的个数为 \(2(n-i-j)\),还要划分的段数是 \(m-2j-i\),那么方案就是 \(\dbinom {2(n-i-j)} {m-2j-i}\)

若竖着的都在开头和结尾,那么能插的个数为 \(2(n+1-i-j)\),还要划分的段数是 \(m-2(j-1)-i\),那么方案就是 \(\dbinom {2(n+1-i-j)} {m-2(j-1)-i}\)

接着考虑竖着的方案,发现这个直接用插板法很难求,那么考虑分步来求。先考虑划分为 \(j\) 段,这个直接插板求出,方案是 \(\dbinom {i-1} {j-1}\),再考虑把这 \(j\) 段再插到原来的网格中,可以插在剩下位置的前面,但是再第一个的前面就不行,所以方案是 \(\dbinom {n-i-1} {j/j-1/j-2}\),下面取决于上面的分裂讨论,那么总的方案就是乘积。

考虑枚举有 \(i\) 个竖着的,其中有 \(j\) 段的方案就是三种情况的和乘上竖着的方案。

由于此题数据较大,卡不过去,所以只写了个 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;
}
posted @ 2022-10-27 16:16  PassName  阅读(64)  评论(0编辑  收藏  举报