质因数分解
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) {
st.insert(i);
while(num % i == 0){
num /= i;
}
}
}
if (num > 1) st.insert(num);
最大公约数和最小公倍数 欧几里算法
求最大公约数 gcd
gcd(a,b) = gcd(a, b - a)
int gcd(int a,int b){
return b? gcd(b,a%b):a;
}
求最小公倍数 lcm
int lcm(int a,int b){
return a/gcd(a,b)*b;
}
费马小定理求逆元(洛谷P2613 【模板】有理数取余)
a / b mod p 相当于 a 乘 b的逆元modp
费马小定理:若p为质数,且gcd(b,p)=1,则b^(p-1)≡1(mod p)
即b ^ (p-2)≡b ^-1(mod p)此时b ^ (p - 2)就是b的逆元
所以
(a/b) mod p = (a*b^(p-2)) mod p.
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL mod = 19260817;
LL a, b;
char st[100010];
LL quick_mi(LL a, LL b) {
if (!b) return 1;
LL res = 1;
while (b) {
//cout << (b & 1) << endl;
if (b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res;
}
int main() {
scanf("%s", st);
int len = strlen(st);
for (int i = 0; i < len; i ++) {
a = a * 10 + st[i] - '0';
a %= mod;
}
scanf("%s", st);
len =strlen(st);
for (int i = 0; i < len; i ++) {
b = b * 10 + st[i] - '0';
b %= mod;
}//这题数有点大直接读入不行
//cout << a << " " << b << endl;
//cout << quick_mi(a, b) << endl;
if (!b) {
puts("Angry!");//分母等于0 无理数 无解了
}
else {
//cout << quick_mi(b, mod - 2) << endl;
LL res = (a * quick_mi(b, mod - 2)) % mod;
printf("%lld\n", res);
}
return 0;
}
整数二分差找模板
版本1
当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。
也就是说mid被包括到了左区间,换句话说就是当mid条件满足时往左分时不能把mid去掉
也就是往左边缩区间的时候mid是有可能是答案的,要保留在左区间
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
版本2
当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。
也就是往右边缩区间的时候mid是有可能是答案的,要保留在右区间
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
选择的关键就是看缩区间的时候mid需要包含到哪边
离散化
//离散化 比如说数大范围很大但是比如要1e9这样,但是数的个数却相对很少,时间复杂度和数的大小有关的时候,可以用
//离散化将其离散成大小的序号(排序去重后的序号)
/*离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
举个例子,给出一个集合
{
1
,
100000
,
999
,
15
}
{1,100000,999,15},那么离散化后就是
{
1
,
4
,
3
,
2
}
{1,4,3,2}。
*/
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1000010;
int a[N];//原数组
vector<int> nums;//用来离散化的数组
int get1(int x) {
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = l + r >> 1;
if (nums[mid] >= x) r = mid;
else l = mid + 1;
}
return l;
}//二分查找下标
int get2(int x) {
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}//库函数
int main() {
int T;
cin >> T;
while (T --) {
int n;
cin >> n;
for (int i = 0; i < n; i ++) cin >> a[i], nums.push_back(a[i]);
//排序加去重
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
for (int i = 0; i < n; i ++) cout << get1(a[i]) + 1 << " ";
cout << endl;
nums.clear();
//for (int i = 0; i < n; i ++) cout << get2(a[i]) + 1 << " ";
//cout << endl;
}
return 0;
}
二维前缀和
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
typedef unsigned long long LL;
LL g[N][N];
int n, m;
int main () {
ios::sync_with_stdio(0);
cin.tie(NULL);
int T;
cin >> T;
while (T --) {
cin >> n >> m;
int q;
cin >> q;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
cin >> g[i][j];
g[i][j] = (g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1] + g[i][j]);
//cout << g[i][j] <<" ";
}
LL res = 0;
while (q --) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
x1 --;
y1 --;
LL tmp = (g[x2][y2] - g[x2][y1] - g[x1][y2] + g[x1][y1]);
// cout << tmp << endl;
res = (res ^ tmp);
}
cout << res << endl;
}
return 0;
}
环形纸牌均分
P2512 [HAOI2008] 糖果传递
第一个能给最后一个传就不能按线性这种分析了,他这情况就多了,不能像上面这样分析了
题解:https://www.luogu.com.cn/problem/solution/P2512
直接看吧反正就是转化成了上一个从数轴上找一个点使得这个点到所有点的距离最短
这其实也是当模板背了 环形纸牌模板
首先,最终每个小朋友的糖果数量可以计算出来,等于糖果总数除以n,
假设C1=A1减去最终每个小朋友的糖果数量,设Xi表示第i个小朋友给了第i-1个小朋友Xi颗糖果,
题解写错了 看代码应该是这样的
C1=-ave
C2=A1−2ave
Cn=A1+A2+…+An-1 −n⋅ave
我们希望Xi的绝对值之和尽量小,
即|X1| + |X1-C1| + |X1-C2| + ……+ |X1-Cn-1|要尽量小。注意到|X1-Ci|的几何意义是数轴上的点X1到Ci的距离,
所以问题变成了:给定数轴上的n个点,找出一个到他们的距离之和尽量小的点,而这个点就是这些数中的中位数
,证明应该都懂。
源码:
#include<iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const int inf = 0x7fffffff;
LL a[N], c[N];
LL n, ave, res, mid;
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
ave += a[i];
}
ave /= n;
for (int i = 1; i <= n; i ++) c[i] = c[i - 1] + a[i - 1] - ave;
// c[1] = -ave
// c[2] = a1 - 2*ave
// c[3] = a1 + a2 - 3*ave
// c[n] = a1 + ... + an-1 - n*ave
//重点就是知道c数组是咋求的就行,然后再求一个点到c数组的距离最短,就是上一题证明的中位数
sort(c+1,c+n+1);
mid=c[(n+1)/2];
for(int i=1;i<=n;++i)
res+=abs(mid-c[i]);
printf("%lld",res);
return 0;
}
*/
Acwing 124 数的进制转换
/*看视频能看懂
一个数A转换成b进制数,用短除法,就是A % b是该b进制数的最后一位,[A / b(下取整)] % b是该b进制数的最后一位
为该b进制数的倒数第二位数,[[A / b(下取整)]]/b % b是该b进制数的倒数第三位以此类推...
还要注意一个a进制数做除法的时候要进位的时候要乘a,特别的10进制数要乘10
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
int T;
cin >> T;
while (T --) {
int a, b;
string a_line, b_line;
cin >> a >> b >> a_line;
vector<int> num_a;
for (auto &c : a_line) {
if (c >= '0' && c <= '9') num_a.push_back(c - '0');
if (c >= 'A' && c <= 'Z') num_a.push_back(c - 'A' + 10);
if (c >= 'a' && c <= 'z') num_a.push_back(c - 'a' + 36);
}
reverse(num_a.begin(), num_a.end());
vector<int> num_b;
while (num_a.size()) {
int t = 0;
for (int i = num_a.size() - 1; i >= 0; i --) {
num_a[i] += t * a;
t = num_a[i] % b;
num_a[i] /= b;
}//做了一次除法
num_b.push_back(t);
while (num_a.size() && !num_a.back()) num_a.pop_back();
}
reverse(num_b.begin(), num_b.end());
for (auto x : num_b)
{
if (x <= 9) b_line += char('0' + x);
else if (x <= 35) b_line += char('A' + x - 10);
else b_line += char('a' + x - 36);
}
cout << a << ' ' << a_line << endl;
cout << b << ' ' << b_line << endl;
cout << endl;
}
return 0;
}
数论分块
数论分块可以快速计算一些含有除法向下取整的和式(即形如 它主要利用了富比尼定理(Fubini’s theorem),将 n / i (向下取整) 相同的数打包同时计算。
https://oi-wiki.org/math/number-theory/sqrt-decomposition/
原理在于对于 n/i(向下取整) (对i从1到n求和)时,将 n/i(向下取整)相同的数打包同时计算原因在于 n/i(向下取整)的结果是呈现块状的,且
P2261 [CQOI2007] 余数求和
首先知道一点
a%b 可以表示为
a−b∗⌊ a / b ⌋,写过高精取模的人应该都知道
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1e9 + 10;
typedef long long LL;
int main () {
LL n, k;
cin >> n >> k;
LL l = 1;
LL r;
LL res = n * k;
while (l <= n) {
if (k / l) r = min(n, k / (k / l));
else r = n;
//for (int i = l; i <= r; i ++) cout << k / l << " ";
//cout << endl;
res -= (k / l) * ((r - l + 1) * (r + l) / 2);//后面这部分是等差数列求和,首项加末项乘项数除2
l = r + 1;
}
cout << res << endl;
//for (int i = 1; i <= 10; i ++) cout << 10 / i << endl;
return 0;
}
狄利克雷前缀和
洛谷P5495 【模板】Dirichlet 前缀和
#include <bits/stdc++.h>
using namespace std;
const int N=2e7+5;
bool p[N];
#define uint unsigned int
uint seed,b[N],n,ans;
inline uint getnext(){
seed^=seed<<13;
seed^=seed>>17;
seed^=seed<<5;
return seed;
}
int main()
{
cin>>n>>seed;
for(int i=1;i<=n;i++)b[i]=getnext();
p[1]=1;
for(int i=1;i<=n;i++)
{
if(!p[i])//i是不是素数,不是素数才这样做
{
for(int j=1;i*j<=n;j++)
{
b[i*j]+=b[j];
p[i*j]=1;
}
}
ans^=b[i];
}
cout<<ans<<endl;
return 0;
}
区间合并(http://ybt.ssoier.cn:8088/problem_show.php?pid=1236)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N =50010;
int n;
PII q[N];
int main() {
scanf("%d", &n);
//int len = 0;
for (int i = 0; i < n; i ++) {
scanf("%d%d", &q[i].first, &q[i].second);
//len = max(len, q[i].second);
}
sort(q, q + n);
int ed = q[0].second;
for (int i = 1; i < n; i ++) {
if (q[i].first <= ed) {
ed = max(ed, q[i].second);
}
else {
printf("no\n");
return 0;
}
}
printf("%d %d\n", q[0].first, ed);
}
多路归并(堆,优先队列)
AcWing 1262. 鱼塘钓鱼(每日一题)
## 没用堆
// 这题关键是弄明白不能返回,然后拆解成n种情况,进而用多路归并解决问题
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int n, T;
int a[N], d[N], t[N], spend[N];
int get(int k)
{
return max(0, a[k] - d[k] * spend[k]);
}
int work(int n, int T) {
if (T <= 0 ) return -1;
int res = 0;
memset(spend, 0, sizeof spend);
//cout << T << endl;
while (T --) {
int tt = 1;
for (int i = 2; i <= n; i ++)
if (get(tt) < get(i)) tt = i;
res += get(tt);
spend[tt] += 1;
}
return res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++) scanf("%d", &d[i]);
for (int i = 2; i <= n; i ++) {
scanf("%d", &t[i]);
t[i] += t[i - 1];
}
int res = 0;
scanf("%d", &T);
for (int i = 1; i <= n; i ++)
res = max(res, work(i, T - t[i]));
printf("%d\n", res);
return 0;
}
## 用堆(优先队列)实现
// 这题关键是弄明白不能返回,然后拆解成n种情况,进而用多路归并解决问题
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 110;
int n, T;
int a[N], d[N], t[N], spend[N];
/*
int get(int k)
{
return max(0, a[k] - d[k] * spend[k]);
}
int work(int n, int T) {
if (T <= 0 ) return -1;
int res = 0;
memset(spend, 0, sizeof spend);
//cout << T << endl;
while (T --) {
int tt = 1;
for (int i = 2; i <= n; i ++)
if (get(tt) < get(i)) tt = i;
res += get(tt);
spend[tt] += 1;
}
return res;
}
*/
bool cmp(pair<int, int> a, pair<int, int> b) {
return a.first > b.first;
}
int work(int n, int T){
int res = 0;
priority_queue<pair<int,int>, vector<pair<int, int>>,less<pair<int, int>>> q;//建立大根堆
for(int i = 1; i <= n; i ++) {
pair<int, int> temp = {a[i], i};
q.push(temp);
}
for (int i = 0; i < T; i ++) {
auto temp = q.top();
q.pop();
res += temp.first;
q.push({max(0,temp.first - d[temp.second]), temp.second});
}
return res;
// priority_queue<int, vector<int>, greater<int>> q;建立小根堆
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++) scanf("%d", &d[i]);
for (int i = 2; i <= n; i ++) {
scanf("%d", &t[i]);
t[i] += t[i - 1];
}
int res = 0;
scanf("%d", &T);
for (int i = 1; i <= n; i ++)
res = max(res, work(i, T - t[i]));
printf("%d\n", res);
return 0;
}
作者:cypher
链接:https://www.acwing.com/activity/content/code/content/8022645/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
上升子序列的个数
//求上升子序列的数列 不重复
// f[i] 表示第i个字母结尾的上升序列的个数
//则 任何i前面s[j] < s[i] 则有 f[i]包含f[j]的数量
//比如说 ac abc bc c ,如果s[i] c 则这4个都能和s[i]构成上升序列
//避免重复,如果s[i] == s[j] 那么 s[j]直接等于0就行了,因为和s[j]构成的
//上升序列一定也能和s[i]构成,但是j后面的和s[i]构成的逆序对和s[j]构不成,所以
//让s[j]为0就行
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
char s[N];
int f[N];
int main() {
scanf("%s", s);
int n = strlen(s);
for (int i = 0; i < n; i ++) {
//cout << i << endl;
// << s[i];
f[i] = 1;//本身就是一个上升序列
for (int j = 0; j < i; j ++) {
if (s[j] < s[i]) f[i] += f[j];
if (s[j] == s[i]) f[j] = 0;
}
}
int res = 0;
for (int i = 0; i < strlen(s); i ++) res += f[i];
printf("%d\n", res);
return 0;
}
97. 约数之和(分解质因数,及其性质约数个数的算和约数和的计算,以及递归求等比数列的和以及快速mi)
/* 首先要知道约数和定理
n = p1 ^ a1 * p2 ^ a2 * p3 ^ a3 * ... * pm ^ am 这称为质因数分解
那么n一共(a1 + 1) * (a2 + 1) * (a3 + 1) * ...*(am + 1)个约数
他们的和为(p1 ^ 0 + p1^1 + p1^2 + .. + p1 ^ a1) * (p2^0 + p2 ^ 1 + ..+ p2^a2) + ...
+ (pm^1 + pm^2 + ... + pm^am) (因为展开就是每个约数的任意个数组合他相加,他们都是n
的约数)
其次要知道n的质因数分解对 n^B 质因数分解有 = p1 ^ a1 * B + p2^a2 *B + ... + pm^am*B
再次要知道用递推的方法对等比数列求和
sum(p, k) = p ^ 0 + p^1 + p^2 + p^3 + ... + p^k
首先如k为奇数即一共有偶数次项那么可以改写成
= p ^ 0 + p ^ 1 + p^2 + ... p ^ k/2 + p ^ k/2 + 1 + p ^k/2+2 + p^k/2+3 +...
+p^k/2 + k/2 + 1 这里k/2是k/2向下取整 因此k为奇数时候2/k + 2/k + 1= k
比如 3/2 = 1 , 1 + 1+ 1= 3, 15 /2 = 7, 7 + 7 + 1 = 15
= p ^ 0 + p ^ 1 + p^2 + ... p ^ k/2 + p^k/2 + 1*(p ^ 0 + p ^ 1 + p^2 + ... p ^ k/2)
= sum(p, k/2) + p^k/2 + 1 * sum(p, k/2)
当k为偶数时候即一共有奇数项 1 + p(p0 + p^1 + .. + p^k - 1)而k - 1又是基数了就变成了
1 + p*sum(p, k - 1)
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int mod = 9901;
int qmi(int a, int b) {
a %= mod;
int res = 1;
while (b) {
if (b & 1) res = (res * a) % mod;
b >>= 1;
a = (a * a) % mod; // a 只会有 1 2 4 16 16 * 16 因为二进制对应的位数也是这样的
}
return res;
}
int sum(int p, int k) {
if (k == 0) return 1;
if (k % 2 == 0) return (p % mod * sum(p, k - 1) % mod + 1) % mod;
else return sum(p, k / 2) % mod * (1 + qmi(p, k / 2 + 1)) % mod;
}
int main() {
int A, B;
cin >> A >> B;
int res = 1;
for (int i = 2; i <= A; i ++) {
int s = 0;
while (A % i == 0) {
s ++;
A /= i;
}
if (s) res = (res * sum(i, s * B) % mod) % mod;
}
if (!A) res = 0;
cout << res << endl;
return 0;
}
树状数组1)修改一个元素的值,2)查询一个区间的和
241. 楼兰图腾
树状数组实现的使log(n)的时间复杂度完成:1)修改一个元素的值,
2)查询一个区间的和。
直接暴力求解
这个问题最原始的解决方法是直接用一个普通的数组来实现。即:
修改:直接修改对应元素的值。复杂度为O(1)。
求和:遍历整个查询区间进行求和,复杂度为O(N)。
修改低求和高
前缀和求解
修改:例如修改了第5个位置的数,则前缀和数组需要修改第 5 ~ n 个位置的的数。(n 为数组长度)
假设修改的位置是 i,则需要修改前缀和数组的第 i 个位置 第 i 个位置之后的所有数。复杂度为O(N)。
求和:假设数组前缀和数组是s。例如求 [l, r],只需要 s[r] - s[l - 1] 。
即区间右端点对应的前缀和 减去 区间左端点对应的前缀和。复杂度为O(1)。
可以看到,在前缀和方式之下,求和的时间开销很低,但是修改的时间开销很高。在极端情况下,如果全部为求和,那么单次操作的平均复杂度为O(1);如果全部为修改,那么单次操作的平均复杂度为O(N)。如果我们假设修改和查询一样多,各占50%,那么平均复杂度仍然为O(N)。
求和低 修改数高
而树状数组是一个折中的算法
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 200010;
int n;
int a[N];
int tr[N];
int G[N], L[N];
// 树状数组模板
int lowbit (int x) {
return x & -x;
}
void add(int x, int c) {
for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
} // 对 x 进行加c操作
int sum(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}// 对1到x求区间和
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; i++) {
int t = a[i];
L[i] = sum(t - 1);
G[i] = sum(n) - sum(t); // 类似前缀和求区间和
add(t, 1);
}
memset(tr, 0, sizeof tr);
LL res1 = 0, res2 = 0;
for (int i = n; i >= 1; i --) {
int t = a[i];
res1 += G[i] * (LL)(sum(n) - sum(t));
res2 += L[i] * (LL)(sum(t - 1));`在这里插入代码片`
add(t, 1);
}
printf("%lld %lld\n", res1, res2);
return 0;
}