标签: 数论

[NOI2010]能量采集 题解

[NOI2010]能量采集 题解

题目地址:洛谷:【P1447】[NOI2010]能量采集 – 洛谷、BZOJ:Problem 2005. — [Noi2010]能量采集

题目描述

栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。
栋栋的植物种得非常整齐,一共有n列,每列有m棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标(x, y)来表示,其中x的范围是1至n,表示是在第x列,y的范围是1至m,表示是在第x列的第y棵。
由于能量汇集机器较大,不便移动,栋栋将它放在了一个角上,坐标正好是(0, 0)。
能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器连接而成的线段上有k棵植物,则能 量的损失为2k + 1。例如,当能量汇集机器收集坐标为(2, 4)的植物时,由于连接线段上存在一棵植物(1, 2),会产生3的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植物,则能量损失为1。现在要计算总的能量损失。

输入输出格式

输入格式:
仅包含一行,为两个整数n和m。

输出格式:
仅包含一个整数,表示总共产生的能量损失。

输入输出样例

输入样例#1:

5 4

输出样例#1:

36

输入样例#1:

3 4

输出样例#1:

20

说明

对于10%的数据:1 ≤ n, m ≤ 10;
对于50%的数据:1 ≤ n, m ≤ 100;
对于80%的数据:1 ≤ n, m ≤ 1000;
对于90%的数据:1 ≤ n, m ≤ 10,000;
对于100%的数据:1 ≤ n, m ≤ 100,000。

题解

首先有一个反演求gcd(a, b)=k的数对(a, b)个数的做法,就是经典反演题[HDU1695]GCD 题解 | KSkun’s Blog。我们把这个方法直接套用过来,枚举gcd可能的值,这个值应该是[1, \min(n, m)]范围内的,求一遍加在一起就好了。
NOI模拟的时候,没注意爆了个int,尴尬。下回要注意一下别乘爆了。

代码

// Code by KSkun, 2018/5
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <functional>
#include <utility>

typedef long long LL;

const int MAXN = 100005;

bool notprime[MAXN];
int prime[MAXN], mu[MAXN], ptot;

inline void sieve(int n) {
    mu[1] = 1;
    for(int i = 2; i <= n; i++) {
        if(!notprime[i]) {
            prime[++ptot] = i;
            mu[i] = -1;
        }
        for(int j = 1; j <= ptot && prime[j] * i <= n; j++) {
            int k = prime[j] * i;
            notprime[k] = true;
            if(i % prime[j] == 0) {
                mu[k] = 0; break;
            } else {
                mu[k] = -mu[i];
            }
        }
    }
}

int n, m;

int main() {
    scanf("%d%d", &n, &m);
    if(n > m) std::swap(n, m);
    sieve(n);
    LL res = 0;
    for(int i = 1; i <= n; i++) {
        int p = n / i, q = m / i;
        for(int j = 1; j <= p; j++) {
            res += 1ll * mu[j] * (p / j) * (q / j) * (2 * (i - 1) + 1);
        }
    }
    printf("%lld", res);
    return 0;
}
[HDU1695]GCD 题解

[HDU1695]GCD 题解

题目地址:HDUOJ:Problem – 1695

题目描述

Given 5 integers: a, b, c, d, k, you’re to find x in a…b, y in c…d that GCD(x, y) = k. GCD(x, y) means the greatest common divisor of x and y. Since the number of choices may be very large, you’re only required to output the total number of different number pairs.
Please notice that, (x=5, y=7) and (x=7, y=5) are considered to be the same.
Yoiu can assume that a = c = 1 in all test cases.
求[1, b]中的整数x与[1, d]中的整数y使得gcd(x, y)=k的数对(x, y)的数量。注意(5, 7)和(7, 5)只能算一对。

输入输出格式

输入格式:
The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 3,000 cases.
Each case contains five integers: a, b, c, d, k, 0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000, as described above.

输出格式:
For each test case, print the number of choices. Use the format in the example.

输入输出样例

输入样例#1:

2
1 3 1 5 1
1 11014 1 14409 9

输出样例#1:

Case 1: 9
Case 2: 736427

题解

本题需要的数学姿势:莫比乌斯反演原理及应用 | KSkun’s Blog
由于gcd(xk, yk)=k等价于gcd(x, y)=1,我们只需要求出[1, b/k]和[1, d/k]中互质的数对数即可。这样可以先行缩小b和d的范围。
我们构造函数f(n)为gcd(x, y)=n的数对数,而答案就是f(1)。这个函数求值很不好办,我们考虑构造另外一个函数F(n)为n|gcd(x, y)的数对数。我们先令p=b/k,q=d/k,很容易知道
F(n) = \frac{p}{n} \cdot \frac{q}{n}
然后研究f(n)和F(n)的关系,发现满足下面这个关系
F(n) = \sum_{n|d} f(d)
然后就可以用莫比乌斯反演
f(n) = \sum_{n|d} \mu(\frac{d}{n}) F(d)
由于我们求的是f(1),所以实际上是这样求的
f(1) = \sum_{i=1}^{\min(p, q)} \mu(i) \cdot \frac{p}{i} \cdot \frac{q}{i}
而由于题目要求去重,我们发现只有当x和y都处于 [1, \min(b, d)] 中时才会发生重复,因此我们令p=q=\min(b/k, d/k)再求一遍f(1),减去它的一半即可。

代码

// Code by KSkun, 2018/4
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005;

bool notprime[MAXN];
int prime[MAXN], miu[MAXN], ptot;

inline void sieve() {
    miu[1] = 1;
    for(int i = 2; i <= MAXN; i++) {
        if(!notprime[i]) {
            prime[ptot++] = i;
            miu[i] = -1;
        }
        for(int j = 0; j < ptot && prime[j] * i <= MAXN; j++) {
            int k = prime[j] * i;
            notprime[k] = true;
            if(i % prime[j] == 0) {
                miu[k] = 0; break;
            } else {
                miu[k] = -miu[i];
            }
        }
    }
}

int T, a, b, c, d, k;

int main() {
    sieve();
    T = readint();
    for(int kase = 1; kase <= T; kase++) {
        LL all = 0, rep = 0;
        a = readint(); b = readint(); c = readint(); d = readint(); k = readint();
        if(!k) {
            printf("Case %d: 0\n", kase);
            continue;
        }
        b /= k; d /= k;
        if(b > d) std::swap(b, d);
        for(int i = 1; i <= b; i++) {
            all += 1ll * miu[i] * (b / i) * (d / i);
        }
        for(int i = 1; i <= b; i++) {
            rep += 1ll * miu[i] * (b / i) * (b / i);
        }
        printf("Case %d: %lld\n", kase, all - rep / 2);
    }
    return 0;
}
[CQOI2016]密钥破解 题解

[CQOI2016]密钥破解 题解

题目地址:洛谷:【P4358】[CQOI2016]密钥破解 – 洛谷、BZOJ:Problem 4522. — [Cqoi2016]密钥破解

题目描述

一种非对称加密算法的密钥生成过程如下:

  1. 任选两个不同的质数p,q
  2. 计算N=pq,r=(p−1)(q−1)
  3. 选取小于r,且与r互质的整数e
  4. 计算整数d,使得ed \equiv 1 \pmod{r}
  5. 二元组(N,e)称为公钥,二元组(N,d)称为私钥。

当需要加密消息n时,(假设n是一个小于N的整数,因为任何格式的消息都可转为整数表示),使用公钥(N,e),按照n^e \equiv c \pmod{N}运算,可得到密文c,对密文c解密时,用私钥(N,d),按照c^d \equiv n \pmod{N}运算,可得到原文n。算法正确性证明省略。
由于用公钥加密的密文仅能用对应的私钥解密,而不能用公钥解密,因此称为非对称加密算法。通常情况下,公钥由消息的接收方公开,而私钥由消息的接收方自己持有。这样任何发送消息的人都可以用公钥对消息加密,而只有消息的接收方自己能够解密消息。
现在,你的任务是寻找一种可行的方法来破解这种加密算法,即根据公钥破解出私钥,并据此解密密文。

输入输出格式

输入格式:
输入文件内容只有一行,为空格分隔的三个正整数e,N,c。

输出格式:
输出文件内容只有一行,为空格分隔的两个整数d,n。

输入输出样例

输入样例#1:

3 187 45

输出样例#1:

107 12

说明

对于30%的数据,e,N,c \leq 2^{20}
对于100%的数据,e,N,c \leq 2^{62}

题解

本题需要的数学姿势有:Miller-Rabin素性测试与Pollard’s rho算法 | KSkun’s Blog
我一看,N马上一个Pollard-Rho套上去分解因数。p和q都有了你还愁什么d,扩欧一发逆元就出来了。
这不是个板子裸题吗……

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>

#include <algorithm>

typedef long long LL;

inline LL myabs(LL x) {
    return x < 0 ? -x : x;
}

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline LL mul(LL a, LL b, LL p) {
    a %= p; b %= p;
    LL res = 0;
    while(b) {
        if(b & 1) res += a;
        if(res >= p) res %= p;
        a <<= 1; b >>= 1;
        if(a >= p) a %= p;
    }
    return res;
}

inline LL fpow(LL n, LL k, LL p) {
    LL res = 1; n %= p;
    for(; k; n = mul(n, n, p), k >>= 1) {
        if(k & 1) res = mul(res, n, p);
    }
    return res;
}

inline LL gcd(LL a, LL b) {
    if(a < b) std::swap(a, b);
    if(!b) return a;
    return gcd(b, a % b);
}

LL p, q;

inline LL pollard_rho(LL x, LL c) {
    LL xi = 1ll * rand() * rand() % (x - 1) + 1, y = xi, i = 1, k = 2;
    for(;;) {
        i++;
        xi = (mul(xi, xi, x) + c) % x;
        LL g = gcd(myabs(y - xi) % x, x);
        if(g > 1 && g < x) return g;
        if(xi == y) return x;
        if(i == k) {
            y = xi;
            k <<= 1;
        }
    }
    return x;
}

inline LL exgcd(LL a, LL b, LL &x, LL &y) {
    if(!b) {
        x = 1; y = 0; return a;
    }
    LL res = exgcd(b, a % b, x, y);
    LL t = x; x = y; y = t - a / b * y;
    return res;
}

LL e, N, c;

int main() {
    srand(time(NULL));
    e = readint(); N = readint(); c = readint();
    LL p = N;
    while(p >= N) p = pollard_rho(N, 1ll * rand() * rand() % (N - 1) + 1);
    LL r = (p - 1) * (N / p - 1);
    LL d, y;
    exgcd(e, r, d, y);
    d = (d % r + r) % r;
    LL n = fpow(c, d, N);
    printf("%lld %lld", d, n);
    return 0;
}