快速幂

前言

有两种思想,但代码一样,指数折半或者利用二进制,即从十进制和二进制两种方向理解

两种方法求解过程,以2的11次为例

指数折半2.11

= 2 * 4^5
=2 * 4 * 8 ^ 2
=2 * 4 * 16

二进制2^11,1011嘛

=2 * 4 * 16

指数折半

核心思想就是每一步都把指数分成两半,而相应的底数做平方运算

3^10=(3 * 3) ^ 5

3^10=9 ^ 5

现在我们的问题是如何把指数5变成原来的一半,5是一个奇数,5的一半是2.5,但是我们知道,指数不能为小数,因此我们不能这么简单粗暴的直接执行5/2,然而,这里还有另一种方法能表示9^5

9^5=( 9^4 )*( 9^1 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
long long quick_power(int base, int power)
{
long long ans = 1;
while (power > 0) //指数大于0进行指数折半,底数变其平方的操作
{
if (power & 1) //指数为奇数
{
power -= 1; //指数减一,不写也可以int自动取整
ans *= base % mod; //分离出当前项并累乘
}
power >>= 1; //指数折半
base *= base % mod; //底数变其平方
}
return ans % mod;
}

二进制

比如n的11次,把11用二进制拆分,8+0+2+1,即1011.

从低位开始递推,1次1次等于2次,2次2次等于4次……

不过代码本质也是在做底数翻倍,比如2 ^ 1到2 ^ 2就是2 ^ 1到4 ^ 1,和指数折半是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
long long quick_power(int base, int power)
{
long long ans = 1; //用于存储项累乘与返回最终结果,由于要存储累乘所以要初始化为1
while (power > 0) //指数大于0说明指数的二进制位并没有被左移舍弃完毕
{
if (power & 1){ //当前位为1
ans *= base % mod; //累乘当前项并存储
}
power >>= 1; //指数位右移(区别主要就在这一步的理解上)
base *= base % mod; //递推,如n的2次变为n的4次
}
return ans % mod;
}
阅读更多...

gcd和lcm和裴蜀定理

gcd(辗转相除法)

我们将两数中较大的那一个看作是被除数A,将较小的那一个看作是除数B,二者相除的商记作C,余数记作D。这样我们就可以得到一个等式:A = B×C + D。而辗转相除法的所要用到的原理则是:(A , B) = (B , D)。

(m,n)表示m%n,直到( , )=0,则最后的余数为两个数的最大公因数。

cpp里有库函数可以直接调,也可以手搓一个

1
2
3
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}

lcm( 最小公倍数)

a 乘 b 除以 gcd(a,b) = lcm

裴蜀定理

属于gcd的拓展

定义

a,b为整数,则有整数x和y,使得ax + by = d*gcd(a,b)

阅读更多...

ST表

定义

利用倍增思想,求解静态RMQ(区间最值查询)的算法

利用O(nlog2n)复杂度预处理,进行O(1)的查询

倍增思想: 2的i次增长

模板

先谈一下为什么开log2啊? 如果不开,你长度都是2,4,6这样访问,不浪费空间吗?N*N稍微大点直接爆内存了

而且你初始化个数组查某个数的最大2的次数, 这样和log2比不方便,何况懒得写log2还有自带的函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int st[N][21]; //st[i][j],i记录的是现在遍历的下标,j记录的是log2(长度)
int lg[N]; //因为二进制就是2的i次,所以用来记录很方便,位移就可以了.
int n,m;

void init(){
lg[0] = -1;
for(int i = 1; i <= n; i++){
lg[i] = lg[i>>1] + 1;
}
for(int i = 1; i <= n; i++){
cin>>st[i][0];
}
for(int j = 1; j <= lg[n]; j++){
for(int i = 1; i <= n - (1<<(j-1)) +1; i++){
st[i][j] = min(st[i][j-1], st[i + (1<<(j-1))][j-1]); //j-1就是上一个log2(上一层长度),我们<<位移就为了展开来定下i的位置
}
}
}

int query(int l, int r){
int k = lg[r-l+1];
return min(st[l][k], st[r-(1<<k)+1][k]); //和初始化是同理的,log2上一层就是k
}

int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
init();
for(int i = 0; i< m; i++){
int l,r;
cin>>l>>r;
cout<<query(l,r)<<" ";
}
return 0;
}
阅读更多...

manacher

定义

马拉车,是一种动态规划算法,用来求解字符串S中最长回文子串的长度,时间复杂度O(n)

思路

回文数有两种,奇数和偶数,如aba和abba,奇数好求(中心扩展法)

假设S为abba

我们在相隔字符间加入#,最终为

#a#b#b#a#长度必为奇数

为了编程方便,首位加入哨兵?#a#b#b#a#@

定义数组p[],p[i]代表以s[i]为中心字符的最长回文串半径(自身为1)

?#a#b#b#a#@

11212521211

最大的p[i] - 1就是答案,此处为p[5]-1 = 5- 1 = 4

马拉车主要解决求p的过程,核心思想是回文的镜像也是回文

懒得写了,直接看代码吧

阅读更多...

树形dp

常见定义

dp[i]第一维即根节点编号

一般自底向上类似dfs,即先遍历到底

题目

洛谷p1352上司的舞会(经典)

https://www.luogu.com.cn/problem/P1352

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<bits/stdc++.h>
using namespace std;

const int N = 6005;
vector<int> G[N];
int dp[N][2],val[N],father[N];

//dp定义,dp[i][j]选或不选i节点的值(j为1选,0不选)
void dfs(int u){
dp[u][0] = 0;
dp[u][1] = val[u];
for(int i : G[u]){
dfs(i);//遍历子节点
dp[u][1] += dp[i][0];//父选子不选
dp[u][0] += max(dp[i][0], dp[i][1]);//父不选,子选或不选
}
}

int main(){
int n;
cin>>n;
for(int i = 1; i <= n; i++){
cin>>val[i];
}
for(int i = 1; i < n; i++){
int to,from;
cin>>to>>from;
G[from].push_back(to);
father[to] = from;
}
int t = 1;
while(father[t]) t = father[t];//找根节点
dfs(t);
cout<<max(dp[t][0], dp[t][1]);
return 0;
}
阅读更多...

2023/12/24

最近打了几场周赛,感觉力扣周赛Q3大部分都是图论.蓝桥小白赛的话构造题比较多.

双周赛掉大分Q1和Q3一样,但Q1范围不大,可以直接暴力.枚举子数组直接滑动窗口,当时没想到,直接在想Q3的O(n)解法.

单周赛的Q3是最短路问题, 记得前段时间哪次周赛Q3和这个很像. 一开始直接用dij写,但超时,后面加了个记忆化就过了

蓝桥小白赛Q4取余,首数组没有处理好.

计划这两天把这三题整理一下

阅读更多...

二叉树的对称dfs

经典例题,对称二叉树

https://leetcode.cn/problems/symmetric-tree/description/

代码如图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool compare(TreeNode* left, TreeNode* right){
if (left == NULL && right == NULL) return true;
else if(left == NULL || right==NULL) return false;
else if (left->val != right->val) return false;
return compare(left->left, right->right) && compare(left->right, right->left);
}

class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
return compare(root->left, root->right);
}
};

这题也可以,反转二叉树的奇数层

https://leetcode.cn/problems/reverse-odd-levels-of-binary-tree/description/

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
TreeNode* reverseOddLevels(TreeNode* root) {
dfs(root->left, root->right, true);
return root;
}

void dfs(TreeNode *l, TreeNode *r, bool jud) {
if (l == nullptr) {
return;
}
if (jud) {
swap(l->val, r->val);
}
dfs(l->left, r->right, !jud);
dfs(l->right, r->left, !jud);
}
};

画个图

阅读更多...

2023/12/13

略记一下最近发生的事

最近两天qq和微信相继被ban,呜呼,悲矣

最近主要在看算法和写笔记,顺便在csdn上发发,没学的还有好多,各种板子也难背。上周周赛和双周赛都挺简单的。

乍暖还寒,温度变化很大~

有点颓废了,有时间按题目分集中刷刷题。

接下来干什么呢

算法继续写着,基础算法要复习复习,dp的话理论还差个区间dp写完,多练练。高级数据结构线段树什么的要学一手,还有图论,看一次忘一次,下次系统的写的最短路什么的。

哦,还有cet4,425就行(

阅读更多...

二叉堆

前言

平常都用priority_queue,今天来手搓一下

定义

二叉堆肯定是个完全二叉树(父节点有左右两节点最底层除外,最底层保证从左到右的连续就可)

用数组a来存二叉树吧,一共n个节点, a[1]是根节点,i节点的左节点是2i,右节点是2i+1,如果大于了n就没有子节点.同样反过来i的父节点就是i/2

代码

实现一个最小堆吧.

https://www.luogu.com.cn/problem/P3378洛谷的堆模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<bits/stdc++.h>
using namespace std;

const int N = 1e6;
int a[N];
int len = 0;//记现有节点数的

void push(int x){//上浮(提高优先级的) 添加的优先级最低
a[++len] = x;
int i = len;
while(i > 1 && a[i] < a[i/2]){ //子比父,父不如子啊
swap(a[i], a[i/2]); //老登,辈分该改了
i/=2; //看看爷爷
}
} ;

void pop(){ //下沉(降低优先级的) 删除的那个优先级最高
a[1] = a[len--]; //根节点直接换成叶子节点(因为叶子无牵无挂)
int i = 1;
while(i * 2 <= len){//父比子(至少有左子)
int sonL = i * 2;//左子
int sonR = i * 2 + 1;//右子
if(a[i] < a[sonL] || a[i] < a[sonR]){
if(a[sonL] < a[sonR] || sonR >= len) {//左子好或者右子死掉了
swap(a[i], a[sonL]);
i = sonL;
}
else {//右子好
swap(a[i], a[sonR]);
i = sonR;
}
}
else break; //父强,不用看了
}
};

//懒狗,不想封个类了,就用一次
int main(){
int n;
cin>>n;
while(n--){
int jud;
cin>>jud;
if(jud == 1) {
int x;
cin>>x;
push(x);
}
else if(jud == 2) cout<<a[1]<<endl;
else pop();
}
return 0;
}
阅读更多...

数位dp

写在开头

本文dp数组基础讲的比较简略,看不懂请另查文章. 求解代码部分比较详细.

这篇写的累死我了(雾

题目

洛谷p2602数字计数https://www.luogu.com.cn/problem/P2602

给定两个正整数 a 和 b,求在 [a,b] 中的所有整数中,每个数码(digit)各出现了多少次。

思路

分别求[0,a]和[0,b]各个数码出现的次数,然后cnt[b] - cnt[a]就行了,这题a,b范围能到10^12^暴力显然超时,因为000-099和100-199和600-699这种后面两位中数码出现的次数是一样的,自然想到DP记录多少位对应多少次

dp预处理

定义

dp[i]定义为i位数的每种数字有多少个(例如dp[2]代表00到99中某个数出现的次数,此处加前导0是为了使0和其他数一样)

一图流

数位dp

阅读更多...
  • Copyrights © 2023-2024 Kano
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信