解题报告(十八)数论题目泛做(Codeforces 难度:2000 ~ 3000 + )

整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


繁凡出品的全新系列:解题报告系列 —— 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数( 1 ∼ 5 1 \sim 5 15),以模板题难度 1 1 1 为基准。


这样大家在学习算法的时候就可以执行这样的流程:

%
阅读【学习笔记】 / 【算法全家桶】学习算法 ⇒ \Rightarrow 阅读相应算法的【解题报告】获得高质量题单 ⇒ \Rightarrow 根据一句话题解的提示尝试自己解决问题 ⇒ \Rightarrow 点开详细题解链接学习巩固(好耶)
%
要是26个英文字母用完了我就接上24个希腊字母,我就不信50道题不够我刷的hhh

%
解题报告系列合集:【解题报告系列】超高质量题单 + 题解(ICPC / CCPC / NOIP / NOI / CF / AT / NC / P / BZOJ)

本题单前置知识:《算法竞赛中的初等数论》(ACM / OI / MO)前言、后记、目录索引(十五万字符的数论书)

Codeforces - 数论题目泛做(难度:2000 ~ 3000 + )

题单链接:https://codeforces.com/problemset/page/6?tags=math&order=BY_RATING_DESC

%为了节省篇幅代码我全都放到链接里了( [https://paste.ubuntu.com/](https://paste.ubuntu.com/))

专门挑了一些最简单的题写hhh,好像绝大多数都是数论题,那这篇文章里九只放数论题目吧,其他的题新开一篇解题报告 ~

目录

难度:2000 分

A. CF803F Coprime Subsequences(容斥原理,莫比乌斯函数)

Problem

给定一个 n n n 个数的序列,问你有多少个子序列的 gcd ⁡ = 1 \gcd=1 gcd=1

Solution

序列一共有 n n n 个数,显然一共有 2 n − 1 2^n-1 2n1 个子序列(每个数选或不选减去空集)

考虑容斥。显然答案就是 2 n − 1 2^n-1 2n1 减去 gcd ⁡ > 1 \gcd>1 gcd>1 的子序列个数,设所有含有大于 1 1 1 的因子的序列中的个数为 x x x ,显然 gcd ⁡ > 1 \gcd>1 gcd>1 的子序列的个数为 2 x − 1 2^x-1 2x1。显然只与点的权值有关,而 a [ i ] ≤ 1 0 5 a[i]\le 10^5 a[i]105,考虑维护权值。设序列中的数的最大值为 m m m

  • c n t i cnt_i cnti 表示权值为 i i i 的序列中的数的个数,可以在输入的时候处理一下。

  • s u m i sum_i sumi 表示含有因子 i i i 的数的个数,显然 s u m i = ∑ i ∣ j c n t j \displaystyle sum_i=\sum\limits_{i|j}{cnt_j} sumi=ijcntj,即序列中 i i i 的倍数的个数。我们可以通过枚举倍数在 O ( m l o g m ) O(mlogm) O(mlogm) 的复杂度下计算。

  • f i f_i fi 表示含有因子 i i i 的子序列的个数,显然 f i = 2 s u m i − 1 = 2 ∑ i ∣ j c n t j − 1 \displaystyle f_i=2^{sum_i}-1=2^{\sum\limits_{i|j}{cnt_j}}-1 fi=2sumi1=2ijcntj1,显然 s u m < m ≤ 1 0 5 sum<m\le10^5 sum<m105,我们可以 O ( m ) O(m) O(m) 预处理一下 2 2 2 的次幂。

对于 gcd ⁡ > 1 \gcd>1 gcd>1 的子序列个数,根据奇加偶减的容斥原理,显然为:含有因子 2 2 2 的子序列的个数( f 2 f_2 f2 + + + 含有因子 3 3 3 的子序列的个数( f 3 f_3 f3 + + + 含有因子 5 5 5 的子序列的个数( f 5 f_5 f5 + + + ⋯ \cdots − - 含有因子 2 , 3 2,3 2,3 的子序列的个数( f 6 f_6 f6 − - 含有因子 2 , 5 2,5 2,5 的子序列的个数( f 10 f_{10} f10 − ⋯ -\cdots + 含有因子 2 , 3 , 5 2,3,5 2,3,5 f 30 f_{30} f30) 的子序列的个数 + ⋯ +\cdots +

最终的答案为 2 n − 1 2^n-1 2n1 减去 gcd ⁡ > 1 \gcd>1 gcd>1 的子序列个数,即变为奇减偶加的形式,然后我们可以发现前面 f x f_x fx 的系数实际上就是 μ ( x ) \mu(x) μ(x)(莫比乌斯函数本身就是一个容斥的映射)。

即答案为
2 n − 1 + ∑ i = 2 m μ ( i ) × f i 2^n-1+\sum_{i=2}^{m}\mu(i)\times f_i 2n1+i=2mμ(i)×fi

Time

O ( m l o g m ) , m = max ⁡ { a [ i ] } O(mlogm),m=\max\{a[i]\} O(mlogm),m=max{ a[i]}

Code

// Problem: CF803F Coprime Subsequences
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF803F
// Memory Limit: 250 MB
// Time Limit: 2000 ms 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
const int N = 500007, mod = 1e9 + 7;

typedef long long ll;
int n, m, t;
int a[N], mu[N], cnt[N];
bool vis[N];
int primes[N], tot;
int pow2[N];
ll ans;

int add(int a, int b)
{
   
	return 1ll * a + b >= mod ? 1ll * a + b - mod : 1ll * a + b;
}

int sub(int a, int b)
{
   
	return a - b < 0 ? a - b + mod : a - b;
}

void init(int n)
{
   
	pow2[0] = 1, pow2[1] = 2, mu[1] = 1;
	for(int i = 2; i <= n; ++ i) {
   
		pow2[i] = add(pow2[i - 1], pow2[i - 1]);
		if(vis[i] == 0) {
   
			primes[ ++ tot] = i;
			mu[i] = -1;
		}
		for(int j = 1; j <= tot && i * primes[j] <= n; ++ j) {
   
			vis[i * primes[j]] = true;
			if(i % primes[j] == 0) {
   
				mu[i * primes[j]] = 0;
				break;
			}
			mu[i * primes[j]] -= mu[i];
		}
	}
}

int main()
{
    
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) {
   
		scanf("%d", &a[i]);
		m = max(m, a[i]);
		cnt[a[i]] ++ ;
	}  

	init(N - 7); 
	ans = pow2[n] - 1;
	for(int i = 2; i <= m; ++ i) {
   
		int sum = 0;
		for(int j = i; j <= m; j += i)  
			sum = (1ll * sum + cnt[j]) % mod; 
		ans = (ans + 1ll * mu[i] * (pow2[sum] - 1) % mod + mod) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

B. CF1033D Divisors(Pollard_rho算法)

Problem
在这里插入图片描述

Solution

我原来珍藏的板子T了可还行

直接Pollard_rho算法进行大数质因子分解,因为要求计算的是 A = ∏ a i A=\prod a_i A=ai 的因子个数,所以我们计算一下所有质因子的个数 c n t [ i ] cnt[i] cnt[i],答案显然就是 ∏ ( c n t [ i ] + 1 ) \prod (cnt[i]+1) (cnt[i]+1)(就是对于每一个因子,有不选,选一个,选两个 ⋯ \cdots ,选 c n t [ i ] cnt[i] cnt[i] 个)

1000ms 的时限997ms卡过…

Code

// Problem: D. Divisors
// Contest: Codeforces - Lyft Level 5 Challenge 2018 - Elimination Round
// URL: https://codeforces.com/problemset/problem/1033/D
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)
 
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const ll mod = 998244353;

unordered_map <ll, ll> mp;
  
namespace rho{
   
    const int MAXP = 1000010;
    const int BASE[] = {
   2, 450775, 1795265022, 9780504, 28178, 9375, 325};

    long long seq[MAXP];
    int primes[MAXP], spf[MAXP];


    long long gcd(long long a, long long b) {
   
        int ret = 0;
        while(a) {
   
            for( ; !(a & 1) && !(b & 1); ++ret, a >>= 1, b >>= 1);
            for( ; !(a & 1); a >>= 1);
            for( ; !(b & 1); b >>= 1);
            if(a < b) swap(a, b);
            a -= b;
        }
        return b << ret;
    }

    inline long long mod_add(long long x, long long y, long long m){
   
        return (x += y) < m ? x : x - m;
    }

    inline long long mod_mul(long long x, long long y, long long m){
   
        long long res = x * y - (long long)((long double)x * y / m + 0.5) * m;
        return res < 0 ? res + m : res;
    }

    inline long long mod_pow(long long x, long long n, long long m){
   
        long long res = 1 % m;
        for (; n; n >>= 1){
   
            if (n & 1) res = mod_mul(res, x, m);
            x = mod_mul(x, x, m);
        }

        return res;
    }

    inline bool miller_rabin(long long n){
   
        if (n <= 2 || (n & 1 ^ 1)) return (n == 2);
        if (n < MAXP) return spf[n] == n;

        long long c, d, s = 0, r = n - 1;
        for (; !(r & 1); r >>= 1, s++) {
   }

        for (int i = 0; primes[i] < n && primes[i] < 32; i++){
   
            c = mod_pow(primes[i], r, n);
            for (int j = 0; j < s; j++){
   
                d = mod_mul(c, c, n);
                if (d == 1 && c != 1 && c != (n - 1)) return false;
                c = d;
            }

            if (c != 1) return false;
        }
        return true;
    }

    inline void init(){
   
        int i, j, k, cnt = 0;

        for (i = 2; i < MAXP; i++){
   
            if (!spf[i]) primes[cnt++] = spf[i] = i;
            for (j = 0, k; (k = i * primes[j]) < MAXP; j++){
   
                spf[k] = primes[j];
                if(spf[i] == spf[k]) break;
            }
        }
    }

    long long pollard_rho(long long n){
   
        while (1){
   
            long long x = rand() % n, y = x, c = rand() % n, u = 1, v, t = 0;
            long long *px = seq, *py = seq;

            while (1){
   
                *py++ = y = mod_add(mod_mul(y, y, n), c, n);
                *py++ = y = mod_add(mod_mul(y, y, n), c, n);
                if((x = *px++) == y) break;

                v = u;
                u = mod_mul(u, abs(y - x), n);

                if (!u) return gcd(v, n);
                if (++t == 32){
   
                    t = 0;
                    if ((u = gcd(u, n)) > 1 && u < n) return u;
                }
            }

            if (t && (u = gcd(u, n)) > 1 && u < n) return u;
        }
    }

    vector <long long> factorize(long long n){
   
        if (n == 1) return vector <long long>();
        if (miller_rabin(n)) return vector<long long> {
   n};

        vector <long long> v, w;
        while (n > 1 && n < MAXP){
   
            v.push_back(spf[n]);
            n /= spf[n];
        }

        if (n >= MAXP) {
   
            long long x = pollard_rho(n);
            v = factorize(x);
            w = factorize(n / x);
            v.insert(v.end(), w.begin(), w.end());
        }

        sort(v.begin(), v.end());
        return v;
    }
} 

signed main() {
   
    // Q.init(N - 1);//如果代码超时且仅需要分解大数的质因数可以用这句话,否则不要用
    ll T, n;
    rho :: init();
    scanf("%lld", &T);
    while (T--) {
    
        scanf("%lld", &n);
        vector <ll> res = rho :: factorize(n); 
        for(auto it : res)
        	mp[it] ++ ;
    }
    ll ans = 1;
    for(auto it : mp)
    	ans = (ans * (it.second + 1)) % mod;
    printf("%lld\n", ans);
    return 0;
}

C. CF1244C The Football Season(拓展欧几里德)

Problem

Berland capital team比了 n n n 场比赛,总得分为 p p p 。已知胜一场得 w w w 分,平局 d d d 分,败了 0 0 0分。

答案表示为( x , y , z x,y,z xyz):表示胜了 x x x 场,平局 y y y 场,败了 z z z 场。使得:

x ∗ w + y ∗ d = p x * w + y * d=p xw+yd=p
x + y + z = n x+y+z=n x+y+z=n
若无方案则输出 -1。若有多重方案,输出任意一个即可。

1 ≤ n ≤ 1 0 12 , 0 ≤ p ≤ 1 0 17 , 1 ≤ d < w ≤ 1 0 5 1≤n≤10 ^{12} ,0≤p≤10^{ 17} ,1≤d<w≤10^{ 5} 1n1012,0p1017,1d<w105

Solution

这就是 2000 的题嘛,爱了爱了

直接拓展欧几里得算一下最小的这个整数解即可。

数据较大,中间乘的时候肯定会爆 long long 直接开 __int128 就行了。

Code

// Problem: CF1244C The Football Season
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1244C
// Memory Limit: 250 MB
// Time Limit: 1000 ms  
// Author: 繁凡さん https://fanfansann.blog.csdn.net/
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;
typedef __int128 ll;
const int N = 5e5 + 7;

long long n, m, p, w, d;

ll exgcd(ll a, ll b, ll &x, ll &y)
{
   
	if(b == 0) {
   
		x = 1, y = 0;
		return a;
	}
	ll d = exgcd(b, a % b, x, y);
	ll z = x;
	x = y, y = z - a / b * y;
	return d;
}

int main()
{
   
	scanf("%lld%lld%lld%lld", &n, &p, &w, &d);
	
	ll x, y, gcd;
	gcd = exgcd((ll)d, (ll)w, x, y); 
	if(p % gcd != 0) {
   
		puts("-1");
		return 0;
	}  
	ll x0 = w / gcd, y0 = d / gcd; 
	 
	y = ((p / gcd % x0) * (x % x0) % x0 + x0) % x0;
	x = (p - y * d) / w;

 	if(x < 0 || x + y > n) {
   
		puts("-1");
	}
	else {
   
		cout << (long long)x << " " << (long long)y << " " << n - (long long)y - (long long)x << endl;
	}
	return 0;
}

D. CF900D Unusual Sequences(莫比乌斯反演,组合计数)

难度:2100 分

A. CF484B Maximum Value(模运算,优化剪枝枚举)

Problem

给定一个序列 a i a_i ai ,请找出两个数 i , j i,j i,j,使得 a i ≥ a j a_i \ge a_j aiaj ,并且使得 a i   m o d   a j a_i \bmod a_j aimodaj 的值最大,求这个 a i   m o d   a j a_i\bmod a_j aimodaj 的最大值。

1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ a i ≤ 1 0 6 1 \le n \le 2\times 10^5,1 \le a_i \le 10^6 1n2×105,1ai106

Solution

我们要找的是最大的 a i   m o d   a j a_i\bmod a_j aimodaj

那么显然有:

a m o d    b = a − ⌊ a b ⌋ × b = a − k × b   ( 设  k = ⌊ a b ⌋ ) a\mod b=a-\lfloor\frac{a}{b}\rfloor\times b=a-k\times b\ (\text{设}\ k=\lfloor\cfrac{a}{b}\rfloor) amodb=aba×b=ak×b ( k=ba)

显然 a m o d    b a\mod b amodb 得到的是余数,值一定 b b b 的简化剩余系内,即 0 ≤ a m o d    b = a − k × b < b 0\le a\mod b=a-k\times b<b 0amodb=ak×b<b,所以一定有

a > k b a < ( k + 1 ) b \begin{aligned}&a>kb&\\ &a<(k+1)b\end{aligned} a>kba<(k+1)b

我们可以枚举 a [ i ] = b a[i]=b a[i]=b,然后枚举倍数 k k k,使得 k b < a , a = max ⁡ { a [ i ] } kb<a,a=\max\{a[i]\} kb<a,a=max{ a[i]},对于枚举到的 b b b k k k ,我们可以直接二分找到最大的小于 ( k + 1 ) b (k+1)b (k+1)b 的数作为 a a a

复杂度有点问题,考虑优化。首先这里两个相同权值的点的作用显然是完全一样的,所以我们可以先排序去重,这样我们会遇到的最坏的情况的数据就是 1 , 2 , ⋯ n − 1 1,2,\cdots n-1 1,2,n1,这样枚举 k k k 的均摊复杂度为 n ( n + 1 ) 2 = 1 e 6 , n = 1 e 6 2 = 707 \cfrac{n(n+1)}{2}=1e6,n=\sqrt {\cfrac{1e6}{2}}=707 2n(n+1)=1e6,n=21e6 =707

总复杂度为 O ( n l o g n m ) ≈ 2 e 9 O(nlogn\sqrt m)≈2e9 O(nlognm )2e9 hhh

考虑继续优化:我们可以加上最优化剪枝,当枚举到的 b < a n s b<ans b<ans 的时候,显然求出来的 a m o d    b < b a\mod b<b amodb<b ,所以直接跳过就行了,这样的话我们就可以倒序枚举 b b b,这样我们在遇到的极端数据 1 , 2 , ⋯ n − 1 1,2,\cdots n-1 1,2,n1 的时候,从大到小枚举,前面被更新了之后,基本上后半段遇到的所有的数都可以直接跳过,亲测效率极高。

优化之后跑的贼快,没有一个点超过150ms,在洛谷的提交记录里能排第二 hhh。

Time

O ( n l o g n m ) , m = max ⁡ { a [ i ] } O(nlogn\sqrt m),m=\max\{a[i]\} O(nlognm ),m=max{ a[i]}

Code

// Problem: CF484B Maximum Value
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF484B
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Author: 繁凡さん https://fanfansann.blog.csdn.net/
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;

const int N = 5e5 + 7, M = 2e6 + 7, INF = 1e7;

int n, m;
int ans;
int a[N];
int maxx;

int main()
{
   
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) {
   
		scanf("%d", &a[i]);
		maxx = max(maxx, a[i]);
	}
	sort(a + 1, a + n + 1);
	int num = unique(a + 1, a + n + 1) - a - 1;
	a[num + 1] = maxx;
	
	for(int i = num; i >= 1; -- i) {
   
		if(a[i] <= ans) continue;

		for(int k = 2; (k - 1) * a[i] <= a[num + 1]; ++ k) {
   //a > kb
			int val = k * a[i];//k -> k + 1 
			int aa = lower_bound(a + 1, a + num + 2, val) - a - 1;//a < (k + 1)b
			ans = max(ans, a[aa] % a[i]);
		}
	}
	printf("%d\n", ans);
	return 0;
}

B. CF474F Ant colony(gcd,线段树)

Problem

给出一个长度为 n n n 的序列 s s s q q q 组询问。

每次给定区间 [ l , r ] [l,r] [l,r]

如果 i , j ∈ [ l , r ] i,j \in [l,r] i,j[l,r] s i ∣ s j s_i|s_j sisj i i i 得一分。

问有多少个没有得到满分,即 r − l r-l rl

1   ≤   n , t   ≤   1 0 5 , s i ≤ 1 0 9 1 ≤ n,t ≤ 10 ^5,s_i\le 10^9 1n,t105,si109

Solution

题目中每次询问可以抽象成这样的一个问题:一个序列,问序列中有多少个数可以整除整个序列中所有的数。显然若 x x x 能整除整个序列,则将 x x x 质因数分解以后, x x x 的所有质因子的次方都是整个序列里最小的才能满足整除整个序列,即: x = ∑ i = 1 i = m p i min ⁡ { a i } x=\sum_{i = 1}^{i = m}p_i^{\min\{a_i\}} x=i=1i=mpimin{ ai},欸,这玩意不就是序列里的最大公约数嘛( g c d ( n , m ) = p 1 m i n { α 1 , β 1 } × ⋯ × p k m i n { α k , β k } gcd(n,m)=p_1^{min\{α_1,β_1\}}\times \cdots\times p_k^{min\{α_k,β_k\}} gcd(n,m)=p1min{ α1,β1}××pkmin{ αk,βk}

所以我们直接建一个线段树维护一下区间 gcd ⁡ \gcd gcd 以及有多少个数等于区间 gcd ⁡ \gcd

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值