几道数论和其他题的题解

P3601 签到题

题目背景

我们定义一个函数:qiandao(x)为小于等于x的数中与x不互质的数的个数。

这题作为签到题,给出l和r,要求 求出下面式子结果

∑ i = l r q i a n d a o ( x )   m o d   666623333 \sum_{i=l}^r qiandao(x)\ mod \ 666623333 i=lrqiandao(x) mod 666623333

输入格式

一行两个整数,l、r。

输出格式

一行一个整数表示答案。

说明/提示

对于30%的数据, l , r ≤ 1 0 3 l,r\leq 10^3 l,r103

对于60%的数据, l , r ≤ 1 0 7 l,r\leq 10^7 l,r107

对于100%的数据, 1 ≤ l ≤ r ≤ 1 0 12 , r − l ≤ 1 0 6 1\leq l \leq r \leq 10^{12}, r-l \leq 10^6 1lr1012,rl106

题解

由题意可知
q i a n d a o ( x ) = x − ϕ ( x ) qiandao(x)=x−\phi(x) qiandao(x)=xϕ(x)
其中 ϕ ( x ) \phi(x) ϕ(x)为欧拉函数。
对正整数n,欧拉函数是小于等于n的正整数中与n互质的数的数目。
ϕ ( x ) = ∏ i = 0 k n ( 1 − 1 p i ) \phi(x)=\prod_{i=0}^k n(1-\frac1{p_i}) ϕ(x)=i=0kn(1pi1)
其中 p i p_i pi n n n的质因数。
下面简单推导一下欧拉函数,对于每个质因子 p i p_i pi, p i p_i pi可以把n均分成 n p i \frac n{p_i} pin份,每份有且仅有一个数字含有质因子 p i p_i pi。换句话说就是在1~n中抽到含质因数 p i p_i pi的数的概率为 1 p i \frac 1{p_i} pi1。所以不含n的任一质因数的概率为 ∏ i = 0 k ( 1 − 1 p i ) \prod_{i=0}^k(1-\frac1{p_i}) i=0k(1pi1),概率再乘上n就是互质数字的个数了。比较正规的解释是用容斥原理。不过本质上和用概率来解释没什么区别,都可以用维恩图推出来。

知道欧拉函数后题目就简单了。
通常我们用欧拉筛出质数的同时就求出了欧拉函数。
用如下公式
ϕ ( p ∗ y ) = { p   ϕ ( y ) y 含 有 因 数 p ( p − 1 ) ϕ ( y ) y 不 含 因 数 p \phi(p*y)= \left\{ \begin{array}{rcl} p\ \phi(y) & y含有因数p & \\ (p-1) \phi(y) & y不含因数p & \\ \end{array} \right. ϕ(py)={p ϕ(y)(p1)ϕ(y)ypyp
但本题r的范围过大,r-l范围较小不能直接求欧拉函数,需要用√r范围内的质数去更新 l~r 范围的欧拉函数的值。注意 当剩余的数字remain不为1时说明还剩余一个大于√r 的质数,需要再处理一下。

代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define MOD 666623333
#define phi(x) _phi[(x)-l]
#define remain(x) _remain[(x)-l]

const int N = 1e6 + 5;
ll l, r;
ll prime[N], _phi[N], _remain[N], tot;
bool notPrime[N];
void EulerSieve(ll siz) {
  for (ll i = 2; i < N; i++) {
    if (!notPrime[i]) prime[tot++] = i;
    for (int j = 0; j < tot && i * prime[j] < N; j++) {
      notPrime[i * prime[j]] = true;
      if (i % prime[j] == 0) break;
    }
  }
}
int main(int argc, char const *argv[]) {
  ios::sync_with_stdio(false), cin.tie(0);
  cin >> l >> r;
  EulerSieve(0);
  for (ll i = l; i <= r; i++) remain(i) = phi(i) = i;
  for (int i = 0; prime[i] * prime[i] <= r; i++) {
    ll p = prime[i];
    for (ll j = ((l + p - 1) / p) * p; j <= r; j += p) {
      phi(j) = phi(j) / p * (p - 1);
      while (remain(j) % p == 0) remain(j) /= p;
    }
  }
  ll ans = 0;
  for (ll i = l; i <= r; i++) {
    if (remain(i) != 1) {
      phi(i) = phi(i) / remain(i) * (remain(i) - 1);
    }
    ans = (ans + i - phi(i)) % MOD;
  }
  cout << ans;
  return 0;
}

P1593 因子和题目描述

输入两个整数 a和 b,求 a b a^b ab 的因子和。

由于结果太大,只要输出它对 9901 取模的结果。

输入格式

仅一行,为两个整数 a 和 b。

输出格式

输出一行一个整数表示答案对 9901 取模的结果。

说明/提示

对于全部的测试点,保证 1 ≤ a ≤ 5 × 1 0 7 , 0 ≤ b ≤ 5 × 1 0 7 1 \leq a \leq 5 \times 10^7,0 \leq b \leq 5 \times 10^7 1a5×107,0b5×107

题解

对于一个数 x x x可以分解成质因子乘积
x = p 1 k 1 p 2 k 2 . . . p n k n x=p_1^{k_1}p_2^{k_2}...p_n^{k_n} x=p1k1p2k2...pnkn
定义 f ( x ) f(x) f(x) x x x的因子和,则
f ( x ) = ∏ i = 1 n ∑ k = 0 k i p i k f(x)=\prod_{i=1}^n\sum_{k=0}^{k_i} p_i^k f(x)=i=1nk=0kipik
下面证明一下

y = p 2 k 2 . . . p n k n y=p_2^{k_2}...p_n^{k_n} y=p2k2...pnkn,可以发现 y y y的所有因子乘上 p 1 p_1 p1和乘上 p 1 k 1 − 1 p_1^{k_1-1} p1k11两两配对刚好就是 x x x的因子,依此类推,乘上 p 1 2 p_1^2 p12 p 1 k 1 − 2 p_1^{k_1-2} p1k12也可以配对。
于是得到下面的递推公式。
f ( x ) = f ( y ) ∑ k = 0 k 1 p 1 k f(x)=f(y)\sum_{k=0}^{k_1} p_1^k f(x)=f(y)k=0k1p1k
显然一直拆开,最终就会得到第一个公式。
再用等比数列求和公式得
f ( x ) = ∏ i = 1 n p i k i + 1 − 1 p i − 1 f(x)=\prod_{i=1}^n \frac{p_i^{k_i+1}-1}{p_i-1} f(x)=i=1npi1piki+11
对于 p i k i + 1 p_i^{k_i+1} piki+1用快速幂求解,对于除 p i − 1 p_i-1 pi1,由于结果要取模,所以我们把除 p i − 1 p_i-1 pi1改为乘 p i − 1 p_i-1 pi1的逆元。

费马小定理

根据逆元定义和费马小定理(a不是p的倍数)
a ∗ x ≡ 1 ( m o d   p ) a p − 1 ≡ 1 ( m o d   p ) a∗x \equiv 1(mod\ p) \\ a^{p-1}≡1(mod\ p) ax1(mod p)ap11(mod p)
显然 a a a a p − 2 a^{p-2} ap2互为逆元。至此基本解决。
特别地对于a是p的倍数时。 p i − 1 ≡ 0 ( m o d   9901 ) p_i-1\equiv0 (mod\ 9901) pi10(mod 9901)
p i ≡ 1 ( m o d   9901 ) p_i\equiv1 (mod\ 9901) pi1(mod 9901)
代入原求和公式
s u m = ( p i   m o d   9901 ) 0 + ( p i   m o d   9901 ) + . . . + ( p i   m o d   9901 ) k i s u m = k i + 1 sum=(p_i\ mod\ 9901)^0+(p_i\ mod\ 9901)+...+(p_i\ mod\ 9901)^{k_i} \\ sum=k_i+1 sum=(pi mod 9901)0+(pi mod 9901)+...+(pi mod 9901)kisum=ki+1

扩展欧几里得算法

除了上面求逆元的方法还可以用扩展欧几里得算法。
欧几里得算法用途就是求解 g c d ( a , b ) = a x + b y gcd(a,b)=ax+by gcd(a,b)=ax+by中的 x x x y y y
若x和a再在mod p的意义下互逆。则有下列等式
a x + k p = 1 ax+kp=1 ax+kp=1
下面证明扩展欧几里得算法
有下面两个式子
a x + b y = g c d ( a , b ) b x ′ + ( a   m o d   b ) y ′ = g c d ( b , a   m o d   b ) ax+by=gcd(a,b) \\ bx'+(a\ mod\ b) y'=gcd(b,a\ mod\ b) \\ ax+by=gcd(a,b)bx+(a mod b)y=gcd(b,a mod b)
又因为 g c d ( b , a   m o d   b ) = g c d ( a , b ) gcd(b,a\ mod\ b)=gcd(a,b) gcd(b,a mod b)=gcd(a,b)
b x ′ + ( a   m o d   b ) y ′ = a x + b y 即 : { x = y ′ y = x ′ − ⌊ a b ⌋ y ′ bx'+(a\ mod\ b) y'=ax+by \\ 即 : \\ \left\{ \begin{array}{l} x=y' & \\ y=x'-\lfloor \frac ab \rfloor y' & \\ \end{array} \right. bx+(a mod b)y=ax+by:{x=yy=xbay
这样,要求解 x 和 y,只需要求解 x ′ x' x y ′ y' y,最终找到递归边界 当 b = 0 , x = 1 , y = 0 当 b=0 ,x=1,y=0 b=0,x=1,y=0

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 5e7 + 5;
int a, b;
#define MOD 9901
void getFactors(int x, vector<pair<int, int>> &res) {
  for (int i = 2; i * i <= x; i++) {
    if (x % i == 0) {
      res.push_back({i, 0});
      while (x % i == 0) {
        res.back().second++;
        x /= i;
      }
    }
  }
  if (x != 1) {
    res.push_back({x, 1});
  }
}
int quick_pow(int p, int x) {
  p %= MOD;
  int ans = 1;
  while (x) {
    if (x & 1) {
      ans *= p;
      ans %= MOD;
    }
    p = (p * p) % MOD;
    x >>= 1;
  }
  return ans;
}
int main(int argc, char const *argv[]) {
  ios::sync_with_stdio(false), cin.tie(0);
  cin >> a >> b;
  vector<pair<int, int>> f1;
  getFactors(a, f1);
  int ans = 1;
  for (int i = 0; i < f1.size(); i++) {
    int p = f1[i].first;
    // p^0 + p^1 + ... + p^(n-1)
    int n = f1[i].second * b + 1;
    if (p % MOD == 1) {
      ans *= n, ans %= MOD;
    } else {
      // 求 p-1 逆元 还可用扩展欧几里得算法
      int inverse = quick_pow(p - 1, MOD - 2);
      ans *= inverse, ans %= MOD;
      ans *= ((quick_pow(p, n) - 1 + MOD) % MOD), ans %= MOD;
    }
  }
  cout << ans;
  return 0;
}

P1069 细胞分裂题目描述

Hanks 博士是 BT (Bio−Tech生物技术) 领域的知名专家。现在,他正在为一个细胞实验做准备工作:培养细胞样本。

Hanks 博士手里现在有 N种细胞,编号从 1−N,一个第 i种细胞经过 1 秒钟可以分裂为 S i S_i Si​个同种细胞。现在他需要选取某种细胞的一个放进培养皿,让其自由分裂,进行培养。一段时间以后,再把培养皿中的所有细胞平均分入M个试管,形成M份样本,用于实验。Hanks 博士的试管数M很大,普通的计算机的基本数据类型无法存储这样大的M值,但万幸的是,MMM 总可以表示为 m 1 m_1 m1​的 m 2 m_2 m2次方,即 M = m 1 m 2 M = m_1^{m_2} M=m1m2

现在博士希望知道,选择哪种细胞培养,可以使得实验的开始时间最早。

输入格式

第一行,有一个正整数N,代表细胞种数。

第二行,有两个正整数 m 1 , m 2 m_1,m_2 m1,m2​,以一个空格隔开,即表示试管的总数 M = m 1 m 2 M = m_1^{m_2} M=m1m2​​.

第三行有 N 个正整数,第 i 个数 S i S_i Si表示第 i 种细胞经过 1 秒钟可以分裂成同种细胞的个数。

输出格式

一个整数,表示从开始培养细胞到实验能够开始所经过的最少时间(单位为秒)。

如果无论Hanks博士选择哪种细胞都不能满足要求,则输出整数-1。

题解

这道题直接因式分解还是挺简单的。
但还有另一种做法,迭代提取最大公因数,产生互质的部分,再消去。就是最后递归出口的情况有点复杂。话说这种判断整除的方法我高中数学老师还讲过,以前不觉得,现在越来越觉得我高中数学老师强了。

#include <bits/stdc++.h>
using namespace std;
#define N 30005
int p[N], n, m1, m2, top;
int np1[N], np2[N];
bool vis[N];
void prime_factorization(int x, int res[]) {
  memset(res, 0, sizeof(res[0]) * top);
  for (int j = 0; j < top && p[j] <= x; j++) {
    while (x % p[j] == 0) x /= p[j], res[j]++;
  }
}

int main(int argc, char const *argv[]) {
  ios::sync_with_stdio(false), cin.tie(0);
  cin >> n;
  cin >> m1 >> m2;
  for (int i = 2; i <= m1; i++) {
    if (!vis[i]) p[top++] = i;
    for (int j = 0; j < top && i * p[j] <= m2; j++) {
      vis[i * p[j]] = true;
      if (i % p[j] == 0) break;
    }
  }
  if (m1 == 1 || m2 == 0) {
    cout << "0";
    return 0;
  }
  prime_factorization(m1, np1);
  int ans = INT_MAX;
  for (int i = 0, t; i < n; i++) {
    cin >> t;
    prime_factorization(t, np2);
    int t_ans = -1;
    for (int j = 0; j < top; j++) {
      if (np1[j] == 0) continue;
      if (np2[j] == 0) {
        t_ans = -1;
        break;
      }
      t_ans = max((np1[j] * m2 + np2[j] - 1) / np2[j], t_ans);
    }
    if (t_ans >= 0) ans = min(ans, t_ans);
  }
  cout << (ans == INT_MAX ? -1 : ans);
  return 0;
}

P2671 求和题目描述

一条狭长的纸带被均匀划分出了n个格子,格子编号从1到n。每个格子上都染了一种颜色 c o l o r i color_i colori​用 [ 1 , m ] [1,m] [1,m]当中的一个整数表示),并且写了一个数字 n u m b e r i number_i numberi

定义一种特殊的三元组:(x,y,z),其中x,y,z都代表纸带上格子的编号,这里的三元组要求满足以下两个条件:
x y z 是 整 数 , x < y < z , y − x = z − y c o l o r x = c o l o r z xyz是整数,x<y<z,y-x=z-y \\ color_x=color_z xyz,x<y<z,yx=zycolorx=colorz
满足上述条件的三元组的分数规定为 ( x + z ) × ( n u m b e r x + n u m b e r z ) (x+z) \times (number_x+number_z) (x+z)×(numberx+numberz)。整个纸带的分数规定为所有满足条件的三元组的分数的和。这个分数可能会很大,你只要输出整个纸带的分数除以 10 , 007 10,007 10,007所得的余数即可。

输入格式

第一行是用一个空格隔开的两个正整数n和m。n表纸带上格子的个数,m表纸带上颜色的种类数。

第二行有n用空格隔开的正整数,第i个数字number表纸带上编号为i格子上面写的数字。

第三行有n用空格隔开的正整数,第i个数字color表纸带上编号为i格子染的颜色。

输出格式

一个整数,表示所求的纸带分数除以10007所得的余数。

题解

这道题显然x和y同为奇数或偶数。于是我们把下表按颜色和奇偶性归类,最后遍历挑两个计算分数。
但是是这题数据比较大 O ( n 2 ) O(n^2) O(n2)会超时。
但是我们有下面的公式可以降到O(n)的时间复杂度,

下面推导一下下面公式
∑ i = 1 n ∑ j = i + 1 n ( a i + a j ) ( b i + b j ) = ( n − 2 ) ∑ i = 1 n a i b i + ∑ i = 1 n a i ∑ j = 1 n b j \sum_{i=1}^n \sum_{j=i+1}^n (a_i+a_j)(b_i+b_j) =(n-2)\sum_{i=1}^n a_ib_i +\sum_{i=1}^n a_i \sum_{j=1}^n b_j i=1nj=i+1n(ai+aj)(bi+bj)=(n2)i=1naibi+i=1naij=1nbj
对于 a i b i a_ib_i aibi,可由 ( a i + a j ) ( b i + b j ) (a_i+a_j)(b_i+b_j) (ai+aj)(bi+bj)产生,其中 j j j是自由的,只要满足 i ≠ j i \neq j i=j的约束,所以总共有 n − 1 n-1 n1 j j j可以选择。即 ( n − 1 ) a i b i (n-1)a_ib_i (n1)aibi,对于 a i a j 且 i ≠ j a_ia_j且i \neq j aiaji=j两个变量都固定,则只有一个。写成公式如下
∑ i = 1 n ∑ j = i + 1 n ( a i + a j ) ( b i + b j ) = ( n − 1 ) ∑ i = 1 n a i b i + ∑ i = 1 n a i ∑ j ∈ { x ∣ x ∈ N + , x ≤ n , x ≠ i } b j \sum_{i=1}^n \sum_{j=i+1}^n (a_i+a_j)(b_i+b_j) =(n-1)\sum_{i=1}^n a_ib_i +\sum_{i=1}^n a_i \sum_{j\in \{x|x\in N^+,x\le n,x\neq i\}} b_j i=1nj=i+1n(ai+aj)(bi+bj)=(n1)i=1naibi+i=1naij{xxN+,xn,x=i}bj

#include <bits/stdc++.h>
using namespace std;
#define N 100005
#define MOD 10007
int m, n, ans = 0;
int nums[N];
vector<int> colors[N][2];

void solve(const vector<int> &ids) {
  int n = ids.size();
  if (n < 2) return;
  int sum = 0;
  for (int i = 0; i < n; i++) {
    sum += nums[ids[i]];
    sum %= MOD;
  }
  int ab = 0, tmp = 0;
  for (int i = 0; i < n; i++) {
    int id = ids[i];
    ab += ((id % MOD) * (nums[id] % MOD));
    ab %= MOD;
    tmp += (id * sum);
    tmp %= MOD;
  }
  ab *= (n - 2), ab %= MOD;
  ans += tmp, ans %= MOD;
  ans += ab, ans %= MOD;
}
int main(int argc, char const *argv[]) {
  ios::sync_with_stdio(false), cin.tie(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> nums[i];
  }
  for (int i = 1, t; i <= n; i++) {
    cin >> t;
    colors[t][i & 1].push_back(i);
  }
  for (int i = 1; i <= m; i++) {
    solve(colors[i][0]);
    solve(colors[i][1]);
  }
  cout << ans;
  return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值