整理的算法模板合集: ACM模板
实际上是一个全新的精炼模板整合计划
繁凡出品的全新系列:解题报告系列 —— 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数( 1 ∼ 5 1 \sim 5 1∼5),以模板题难度 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 2n−1 个子序列(每个数选或不选减去空集)
考虑容斥。显然答案就是 2 n − 1 2^n-1 2n−1 减去 gcd > 1 \gcd>1 gcd>1 的子序列个数,设所有含有大于 1 1 1 的因子的序列中的个数为 x x x ,显然 gcd > 1 \gcd>1 gcd>1 的子序列的个数为 2 x − 1 2^x-1 2x−1。显然只与点的权值有关,而 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=i∣j∑cntj,即序列中 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=2sumi−1=2i∣j∑cntj−1,显然 s u m < m ≤ 1 0 5 sum<m\le10^5 sum<m≤105,我们可以 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 2n−1 减去 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 2n−1+i=2∑mμ(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 x,y,z):表示胜了 x x x 场,平局 y y y 场,败了 z z z 场。使得:
x ∗ w + y ∗ d = p x * w + y * d=p x∗w+y∗d=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} 1≤n≤1012,0≤p≤1017,1≤d<w≤105
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 ai≥aj ,并且使得 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 1≤n≤2×105,1≤ai≤106
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=a−⌊ba⌋×b=a−k×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 0≤amodb=a−k×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,⋯n−1,这样枚举 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,⋯n−1 的时候,从大到小枚举,前面被更新了之后,基本上后半段遇到的所有的数都可以直接跳过,亲测效率极高。
优化之后跑的贼快,没有一个点超过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 si∣sj 则 i i i 得一分。
问有多少个没有得到满分,即 r − l r-l r−l。
1 ≤ n , t ≤ 1 0 5 , s i ≤ 1 0 9 1 ≤ n,t ≤ 10 ^5,s_i\le 10^9 1 ≤ n,t ≤ 105,si≤109
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