在这片梦想之地,不堪回首的过去像泡沫一样散去,|

PassName

园龄:3年粉丝:32关注:16

CSP-S 考前数学练习

[HAOI2011] 向量

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

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

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

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

(kw)b+(qc)a=y

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

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

考虑分类讨论:

1.当 k+w,q+c,kw,qc 都是偶数时:

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

2.当 k+w,q+c,kw,qc 都是奇数时:

(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+b2gcd(a,b)y+a+b

3.当 k+w,kw 是偶数,q+c,qc 都是奇数时:

(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,kw 是奇数,q+c,qc 都是偶数时:

(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      (x0,y0,z0) 非负整数解的个数。

d=gcd(A,B,C)

d×(Adx+Bdy+Cdz)=P

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

Adx+Bdy+Cdz=Pd

Adx+Bdy=PdCdz

c=PdCdz

所以 Adx+Bdy=c

可以扩展欧几里得定理求出 Adx+Bdy=gcd(Ad,Bd) 的一组整数特解 x0,y0

Adx0+Bdy0=gcd(Ad,Bd)

将两个式子同时除以 gcd(Ad,Bd) 再乘 c ,可以得到:

Ad×x0cgcd(Ad,Bd)+Bd×y0cgcd(Ad,Bd)=c

得到原方程的整数特解

x1=x0cgcd(Ad,Bd),y1=y0cgcd(Ad,Bd)

因为 Ad×x1+Bd×y1=c

所以 Ad×(x1Bd×gcd(Ad,Bd))+Bd×(y1+Ad×gcd(Ad,Bd))=c

这一步怎么来的呢?因为 (Bd×gcd(Ad,Bd)×Ad)+(Ad×gcd(Ad,Bd)×Bd)=0

给定 x1,y1 变化的倍数,就是得到了通解的形式,其中 s 为任意整数。

Ad×(x1s×Bd×gcd(Ad,Bd))+Bd×(y1+s×Ad×gcd(Ad,Bd))=c

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

x1s×Bd×gcd(Ad,Bd)0

y1+s×Ad×gcd(Ad,Bd)0

我们接着进行转换:

x1s×Bd×gcd(Ad,Bd)

y1s×Ad×gcd(Ad,Bd)

即为

x1×d×gcd(Ad,Bd)Bs

y1×d×gcd(Ad,Bd)As

所以

y1×d×gcd(Ad,Bd)Asx1×d×gcd(Ad,Bd)B

原方程的非负整数解个数即为:x1×d×gcd(Ad,Bd)By1×d×gcd(Ad,Bd)A+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] 古代猪文

题面很长,化简一下就是求 Gd|nCndmod 999911659

我们发现 999911659 是个质数,不难想到欧拉定理,可以得到 Gd|nCnd Gd|nCnd mod 999911658 (mod 999911659)。对于底数 G 和最终取模数 mod 999911659 可以直接用费马小定理求出。

所以现在需要解决的问题就是,如何求出 d|nCnd mod 999911658

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

先对 999911658 进行质因数分解 999911658=2×3×4697×35617

d|nCnd 分别对 999911658 的四个质因数取模,构建同余方程组,求出在模通解 999911658 的通解 t ,那么 t=d|nCndmod 999911658

最终答案即为:Gt mod 999911659

{xd|nCnd(mod 2)xd|nCnd(mod 3)xd|nCnd(mod 4679)xd|nCnd(mod 35617)

然后接着可以用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 的序列为 {a1...an,m},这个题就是在求使 i=1nai×xi+m×xn+1=1 有解的 a1,a2...an 有多少种。

Bézout 定理,可得方程的有解情况就是 gcd(a1,a2...an,m)=1

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

无解时,gcd(a1,a2...,an,m)1

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

但是求无解显然有重复的情况,这个时候可以容斥原理,当 ai 都为 x 的倍数时,此时方案数就是 (mx)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×1 的构成的,而竖着的将将原来的网格分成了若干段。

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

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

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

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

若竖着只有一个在开头结尾,那么能插的个数为 2(nij),还要划分的段数是 m2ji,那么方案就是 (2(nij)m2ji)

若竖着的都在开头和结尾,那么能插的个数为 2(n+1ij),还要划分的段数是 m2(j1)i,那么方案就是 (2(n+1ij)m2(j1)i)

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

考虑枚举有 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;
}

本文作者:PassName

本文链接:https://www.cnblogs.com/spaceswalker/p/16832664.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   PassName  阅读(69)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起