c++算法赛万能模板个人笔记适用蓝桥杯,天梯赛,acm等赛事

算法笔记-更新与2025-3-22

点赞收藏+关注持续更新

算法基础

二分

整数二分

//在一个单调区间里面去找答案

bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
 {
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;//左加右减
    }
    return l;
 }
 // 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
 {
    while (l < r)
    {
        int mid = l + r + 1 >> 1;//如果下方else后面是l则这里加1
        if (check(mid)) l = mid;
        else r = mid - 1;//左加右减
    }
    return l;
 }
浮点数二分
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
 {
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
 }

高精度

高精度比大小
bool cmp(vector A, vector B)
{//A>=B
    if (A.size() != B.size())return A.size() >= B.size();//先比长度
    for (int i = A.size() - 1; i >= 0; i--)
    {
        if (A[i] != B[i])return A[i] >= B[i];//比数值
    }
    return true;//都满足的话就一样大
}
高精度加法
#include
#include
using namespace std;
vector add(vector A, vector B)
{//这里加引用是为了提高效率,不加引用会把整个数组copy一遍,加上引用就不会copy整个数组,就会快很多
    vector C;
    int t=0;
    for (int i = 0; i < A.size() || i < B.size(); i++)
    {
        if (i
高精度减法
bool cmp(vector &A,vector &B)
{//比大小,A>=B
    if(A.size()!=B.size())
        return A.size()>=B.size(); 
    for(int i=A.size()-1;i>=0;i--)
    {
        if(A[i]!=B[i])return A[i]>B[i];
    }
    return true;
}

vector sub(vector &A,vector &B)
{
   vector C;
   int t=0;//进位
   for(int i=0;i=0时和t<0时取个位
        if(t<0)t=1;
        else t=0;
   }
    while(C.size()>1&&C.back()==0)C.pop_back();//去除前导0
    return C;
}
高精度乘法(高X低)
vector mul(vector& A, long long b)
{
    //类似于高精度加法
    vector C;
    //t为进位
    long long t = 0;
    for (int i = 0; i < A.size() || t; i++)
    {
        //不超过A的范围t=t+A[i]*b
        if (i < A.size()) t += A[i] * b;
        //取当前位的答案
        C.push_back(t % 10);
        //进位
        t /= 10;
    }
    //去除前导零
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}
高精度乘法(高X高)
vector mul(vector &A, vector &B) {
    // 创建结果向量 C,初始大小为 A 的大小加上 B 的大小,用于存储乘法结果
    vector C(A.size() + B.size()); 
    // 模拟两个整数的乘法运算,逐位相乘并累加结果
    for (int i = 0; i < A.size(); i++) {
        for (int j = 0; j < B.size(); j++) {
            C[i + j] += A[i] * B[j];
        }
    }
    // 处理进位,将结果向量 C 中的每一位进行进位调整
    for (int i = 0, t = 0; i < C.size(); i++) {
        t += C[i];
        C[i] = t % 10;
        t /= 10;
    }
    // 去除结果向量 C 中的前导零
    while (C.size() > 1 && C.back() == 0) C.pop_back(); 
    return C;
}
高精度除法(高/低)
vector div(vector &A, int b, long long &r) {
    // 存储商的结果
    vector C;
    // 将余数初始化为 0
    r = 0;
    // 从 A 的最高位开始遍历
    for (int i = A.size() - 1; i >= 0; i--) {
        // 将当前位的数字和余数结合起来
        r = r * 10 + A[i];
        // 计算当前位的商并添加到结果向量 C 中
        C.push_back(r / b);
        // 更新余数为取模后的结果
        r %= b;
    }
    // 将结果向量 C 反转,使其变为正确的顺序
    reverse(C.begin(), C.end());
    // 如果结果向量 C 长度大于 1 且最后一位为 0,则不断弹出最后一位直到不满足条件
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    // 返回商的结果向量
    return C;
}

前缀和

【算法笔记】前缀和与差分_前缀和差分-CSDN博客

一维前缀和
   	//预处理:s[i]=a[i]+a[i-1]
	//求区间[l,r]:sum=s[r]-s[l-1]
 	//"前缀和数组"和"原数组"可以合二为一
#include 
using namespace std;
const int N=100010;
 int a[N];
 int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i]=a[i-1]+a[i];
    }
    while(m--){
        int l,r;
        cin>>l>>r;
        cout<
二维前缀和
//计算矩阵的前缀和:s[x][y] = s[x - 1][y] + s[x][y -1] - s[x-1][y-1] + a[x][y]

![[Pasted image 20250320190157.png]]

//计算子矩阵的和:s = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 -1]

![[Pasted image 20250320190150.png]]

//计算矩阵的前缀和:s[x][y] = s[x - 1][y] + s[x][y -1] - s[x-1][y-1] + a[x][y]
//以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
//计算子矩阵的和:s = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 -1]
int s[1010][1010];
int n,m,q;
int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&s[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
    while(q--){
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]);
    }
    return 0;
 }

差分

一维差分
//给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
int a[100010],s[100010];
 int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];   // 读入并计算差分数组
        s[i]=a[i]-a[i-1]; 
    }
    while(m--){
        int l,r,c;
        cin>>l>>r>>c;
        s[l]+=c;
        s[r+1]-=c;// 在原数组中将区间[l, r]加上c
    }
    for(int i=1;i<=n;i++){
        s[i]+=s[i-1];
        cout<
二维差分

二维差分用于在一个矩阵里,快速里把矩阵的一个子矩阵加上一个固定的数。也是直接来修改差分矩阵。试想只要在差分矩阵的( x 1 , y 1 ) 位置加上c,那么以它为左上角,所有后面的元素就都加上了c。要让( x 2 , y 2 ) 的右边和下边的元素不受影响,由容斥原理可以知道,只要在( x 2 + 1 , y 1 ) 和( x 1 , y 2 + 1 ) 位置减去c,再从( x 2 + 1 , y 2 + 1 ) 位置加回c就可以了。

![[Pasted image 20250320190138.png]]

//给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
//S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
const int N = 1e3 + 10;
 int a[N][N], b[N][N];
 void insert(int x1, int y1, int x2, int y2, int c)
 {
    b[x1][y1] += c;
    b[x2 + 1][y1] -= c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y2 + 1] += c;
 }
 int main()
 {
    int n, m, q;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> a[i][j];
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            insert(i, j, i, j, a[i][j]);      //构建差分数组
        }
    }
    while (q--)
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c);//加c
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];  //二维前缀和
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    return 0;
 }

动态规划DP

LIS最长上升子序列

https://www.lanqiao.cn/problems/2049/learning/

朴素LIS O(n^2)
    for(int i=1;i<=n;i++)//O(n^2)
    {
        dp[i]=1;
        for(int j=1;ja[j])dp[i]=max(dp[j]+1,dp[i]);
        }
        ans=max(dp[i],ans);
    }
二分优化LIS O(nlogn)

LCS最长公共子序列

![[Pasted image 20250320185940.png]]

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(a[i]==b[j])dp[i][j]=dp[i-1][j-1]+1;
            else
            {
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
    }

DFS

深搜就是从一个点一直搜到底再往上搜

例题:https://www.luogu.com.cn/problem/P1036

#include 
using namespace std;
int n, k, ans = 0, a[30];
bool sushu(int x)
{
    if (x == 1 || x == 0)return false;
    else if (x == 2)return true;
    else
    {
        for (int i = 2; i * i <= x; i++)
            if (x % i == 0)return false;
        return true;
    }
}

void dfs(int cnt, int sum, int t)
{
    if (cnt == k)
    {
        if (sushu(sum))ans++;
        return;
    }
    else
    {
        for (int i = t; i < n; i++)
            dfs(cnt + 1, sum + a[i], i + 1);
        return;
    }
}

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    dfs(0, 0, 0);
    cout << ans;
    return 0;
}

BFS

广搜就是一层到一层往下搜

例题:https://www.luogu.com.cn/problem/P1451

#include
#include
using namespace std;
int n,m,mv[4][2]={0,1,0,-1,1,0,-1,0},ans;
string mp[120];

void bfs(int x,int y){
    queue > q;
    q.push(make_pair(x,y));
    while(!q.empty()){
        int x1=q.front().first;
        int y1=q.front().second;
        mp[x1][y1]='0';
        q.pop();
        for(int i=0;i<4;i++){
            int xi=mv[i][0]+x1,yi=y1+mv[i][1];
            if(xi<0||yi<0||xi>=n||yi>=m)continue;
            if(mp[xi][yi]!='0'){
                q.push(make_pair(xi,yi));
            }
        }
    }
}

int main()
{
    cin>>n>>m;
    for(int i=0;i>mp[i];
    for(int i=0;i

数据结构

ST表

核心代码

int n,q;
int a[N];
int mx[N][21],mi[N][21];//mx求最大值,mi求最小值 
void ST(int n)
{
	int i,j;
	for(i=1;i<=n;i++)
	{
		mx[i][0]=a[i];
		mi[i][0]=a[i];
	}
	//j  2^j<=n 
	//i  i+2^j-1<=n
	for(j=1;j<=21;j++)
	{
		for(i=1;(i+(1<

求区间最大值

int maxquery(int l,int r)
{
	int k=log2(r-l+1);
	//max(f[l][k],f[r-2^k+1][k]) 
	return max(mx[l][k],mx[r-(1<

求区间最小值

int minquery(int l,int r)
{
	int k=log2(r-l+1);
	return min(mi[l][k],mi[r-(1<
并查集

找根,判断连通

int root(int x)
{
    return pre[x] = (pre[x]==x?x:root(pre[x]));
}
//合并pre[root(x)]=root(y);最坏情况下每次查找O(n)
启发式合并

平均查找O(1),避免树高度过高

void init(){
  for(int i=1;i<=n;++i)pre[i]=i,siz[i]=1;
}

int root(int x){
  return pre[x]==x?x:root(pre[x]);
}

void merge(int x, int y)
{
    int rx = root(x), ry = root(y);
    if(rx == ry)return;//已经连通,无需处理
    //如果rx更大,则交换,可以保证siz[rx] <= siz[ry]
    if(siz[rx] > siz[ry])swap(rx, ry);
    
    //此时有siz[rx] <= siz[ry],所以一定是rx -> ry
    pre[rx] = ry;
    siz[ry] += siz[rx];

    //操作完成后rx将不再作为根,于是它的siz也没有意义了,也不会再变化了
}
单调栈
// 此函数用于找出数组中每个元素右侧第一个比它大的元素
// 参数 n 为数组的长度,参数 a 是一个常量引用,代表输入的数组
vector next_greater(int n, const vector& a) {
    // 初始化结果数组 r,长度为 n,所有元素初始化为 0
    vector r(n, 0);
    // 定义一个栈 st,用于存储数组元素的索引
    stack st;
    // 遍历数组 a
    for (int i = 0; i < n; ++i) {
        // 当栈不为空,并且当前元素 a[i] 大于栈顶元素对应的数组元素 a[st.top()] 时
        while (st.size() && a[i] > a[st.top()]) {
            // 将当前元素 a[i] 作为栈顶元素对应的结果,存储到结果数组 r 中
            r[st.top()] = a[i]; 
            // 弹出栈顶元素
            st.pop();
        }
        // 将当前元素的索引 i 压入栈中
        st.push(i);
    }
    // 返回结果数组 r
    return r;
}

// 此函数用于找出数组中每个元素左侧第一个比它小的元素
// 参数 n 为数组的长度,参数 a 是一个常量引用,代表输入的数组
vector left_smaller(int n, const vector& a) {
    // 初始化结果数组 r,长度为 n,所有元素初始化为 0
    vector r(n, 0); 
    // 定义一个栈 st,用于存储数组元素的索引
    stack st; 
    // 遍历数组 a
    for (int i = 0; i < n; ++i) {
        // 当栈不为空,并且当前元素 a[i] 小于等于栈顶元素对应的数组元素 a[st.top()] 时
        while (st.size() && a[i] <= a[st.top()] ) { 
            // 弹出栈顶元素
            st.pop(); 
        }
        // 如果栈不为空
        if (!st.empty()) { 
            // 将栈顶元素对应的数组元素 a[st.top()] 作为当前元素的结果,存储到结果数组 r 中
            r[i] = a[st.top()]; 
        }
        // 将当前元素的索引 i 压入栈中
        st.push(i);  
    }
    // 返回结果数组 r
    return r;
}

Kruskal

struct Edge {
    int x, y, c;
    bool operator < (const Edge& u)const {
        return c < u.c;
    }
};

int pre[10000];
int root(int x) { return pre[x] == x ? x : root(pre[x]); }

void solve() {
    int n, m;
    cin >> n >> m;
    vector<Edge> es;
    for (int i = 1; i <= m; ++i) {
        int x, y, c;
        cin >> x >> y >> c;
        es.push_back({ x, y, c });
    }

    sort(es.begin(), es.end());

    for (int i = 1; i <= n; ++i) pre[i] = i;
    int ans = 0;
    for (const Edge& e : es) {
        auto x = e.x;
        auto y = e.y;
        auto c = e.c;
        if (root(x) == root(y)) continue;
        ans = max(ans, c);
        pre[root(x)] = root(y);
    }
    cout << ans << '\n';
}

int main() {
    solve();
    return 0;
}

Prim

struct Edge
{
    long long x, c;
    bool operator < (const Edge& u)const
    {
        return c == u.c ? x > u.x : c > u.c;
    }
};
vector g[1000];
long long d[1000];
int n, m;
int prim()
{
    priority_queue pq;
    bool vis[10000];
    d[1] = 0;
    pq.push({ 1, d[1] });
    long long res = 0;
    while (pq.size())
    {
        int x = pq.top().x; pq.pop();
        if (vis[x]) continue;
        vis[x] = true;
        res = max(res, d[x]);
        for (const auto& elem : g[x]) {
            auto& y = elem.x;
            auto& w = elem.c;
            if (vis[y]) continue;
            d[y] = min(d[y], w);
            pq.push({ y, d[y] });
        }
    }
    return res;
}

int main() {
    prim();
    return 0;
}

拓扑排序

有向无环图一定是拓扑序列

	queue q;
	vector g[N];
    for(int i=0;i>u>>v;
        ind[v]++;
        g[u].push_back(v);
    }
	for(int i=1;i<=n;i++){
        if(!inb[i]) q.push(i);//将所有入度为0的点加入队列
    }
    while(!q.empty()){
        int x=q.front();//取出队头的点x,此时它的入度一定为0
        q.pop();
        for(auto y:g[x])//处理x->y
        {
            inb[y]--;
        	if(!inb[y])q.push(a[x]);//如果y入度为0,说明y的所有入点已经处理完成了,直接入队
        }
    }

Floyd

多源最短路,求出每个点与点之间的最短路径

#include 
// 定义ll为long long的别名,方便后续使用
typedef long long ll;
// 定义图的最大顶点数,可根据实际情况调整,这里设为210
const int N = 210;  
// 定义一个很大的数表示无穷大,用于初始化距离为不可达的情况
const ll INF = 1e18;  
// 定义二维数组d,用于存储任意两点之间的最短距离
ll d[N][N];  
// 引入标准命名空间,这样就可以直接使用标准库中的函数和对象,无需std::前缀
using namespace std;
int main() {
    // 禁用C++输入输出流与C标准输入输出流的同步,解除cin与cout的绑定,加快输入输出速度
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    // 用于存储图的顶点数、边数和查询次数
    int n, m, q;
    // 读入图的顶点数n、边数m和查询次数q
    cin >> n >> m >> q;

    // 初始化d数组,将每个点到自身的距离设为0,其余设为无穷大
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) 
                // 自身到自身的距离为0
                d[i][j] = 0; 
            else 
                // 初始时其他点之间距离设为无穷大,表示不可达
                d[i][j] = INF; 
        }
    }
    // 读入边的信息并初始化邻接矩阵,根据边的数量m进行循环
    while (m--) {
        // 用于存储边的起点u、终点v和边的权重w
        ll u, v, w;
        // 读入边的起点、终点和权重
        cin >> u >> v >> w;
        // 更新u到v的距离为较小值(初始或新读入的权重),这里假设图是无向图
        d[u][v] = min(d[u][v], w);
        // 因为是无向图,所以v到u的距离也要更新
        d[v][u] = min(d[v][u], w); 
    }

    // Floyd-Warshall算法核心部分,通过中间节点k更新任意两点间的最短距离
    // 最外层循环,枚举中间节点k
    for (int k = 1; k <= n; k++) {
        // 第二层循环,枚举起点i
        for (int i = 1; i <= n; i++) {
            // 第三层循环,枚举终点j
            for (int j = 1; j <= n; j++) {
                // 通过中间节点k,更新i到j的最短距离
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
    // 处理q次查询
    while (q--) {
        // 用于存储查询的起点st和终点ed
        int st, ed;
        // 读入查询的起点和终点
        cin >> st >> ed;
        // 如果最短距离仍然是无穷大,说明两点不连通
        if (d[st][ed] > INF / 2) 
            // 输出impossible表示两点不可达
            cout << "impossible" << endl; 
        else 
            // 输出两点之间的最短距离
            cout << d[st][ed] << endl; 
    }

    return 0;
}

Dijkstra

求单源最短路,求出所有点距离源点的最短距离

#include 
using namespace std;

// 定义长整型别名,方便后续使用
typedef long long ll;

// 定义最大节点数,可根据实际情况调整
const int MAXN = 3e5 + 10;
// 定义一个极大值,表示无穷远的距离
constexpr ll INF = 1E18;

// 节点数量
int n;
// 边的数量
int m;

// 存储每个节点到源节点的最短距离
ll dist[MAXN];
// 优先队列,用于Dijkstra算法,存储距离和节点编号,按照距离从小到大排序
priority_queue, vector>, greater<>> pq;
// 邻接表,存储图的结构,每个节点对应一个向量,向量中存储与该节点相连的节点和边的权重
vector> graph[MAXN];

// Dijkstra算法实现,start为源节点
void dijkstra(int start) {
    // 将源节点的距离设为0,并加入优先队列
    pq.emplace(0LL, start);

    // 当优先队列不为空时,继续处理
    while (!pq.empty()) {
        // 从优先队列中取出当前距离最小的节点及其距离
        auto [d, u] = pq.top();
        pq.pop();

        // 如果该节点已经有了最短距离,跳过
        if (dist[u] != INF) continue;

        // 更新该节点的最短距离
        dist[u] = d;

        // 遍历当前节点的所有邻接节点
        for (auto [v, w] : graph[u]) {
            // 将邻接节点及其距离加入优先队列
            pq.emplace(d + w, v);
        }
    }
}

int main() {
    // 禁用同步流,提高输入输出效率
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 读取节点数量和边的数量
    cin >> n >> m;

    // 初始化所有节点的最短距离为无穷大
    for (int i = 1; i <= n; i++) {
        dist[i] = INF;
    }

    // 读取每条边的信息,构建图的邻接表
    for (int i = 1; i <= m; i++) {
        int u, v;
        ll w;
        cin >> u >> v >> w;
        // 将边的信息加入邻接表
        graph[u].push_back({v, w});
    }

    // 从节点1开始执行Dijkstra算法
    dijkstra(1);

    // 输出每个节点到源节点的最短距离,如果无法到达则输出 -1
    for (int i = 1; i <= n; i++) {
        if (dist[i] == INF) {
            cout << -1 << ' ';
        } else {
            cout << dist[i] << ' ';
        }
    }

    return 0;
}

数论

gcd

最大公因数,或称最大公约数

__gcd(int a,int b);//官方库
int gcd(int a,int b){
  return b == 0 ? a : gcd(b,a%b);//辗转相除法
}
lcm

最大公倍数

__lcm(int a,int b);//官方库
int lcm(int a,int b){
	return a/gcd(a,b)*b;//先除后乘避免溢出
}
素数
朴素素数
bool isprime(int n){
  if(n<2)return false;//2不是质数
  for(int i=2;i<=n/i;i++)if(n%i==0)return false;//防止溢出
  return true;
}
埃氏筛法

比朴素快很多倍

bool vis[N];//假设true不是素数,被筛掉了
vis[0]=vis[1]=true;
for(int i=2;i<=n/i;i++){
  if(!vis[i])for(int j=i*i;j<=n;j+=i)vis[j]=true;
}
欧拉筛

o(n)最快,可以在1s内筛出约1e7以内的所有质数,埃氏筛法能做的欧拉筛都能做

void euler(int n)
{
    vector primes;
    vis[0] = vis[1] = true;
    for(int i = 2;i <= n; ++i)
    {
        //如果i没有被筛除,说明i是质数,存入vector中
        if(!vis[i])primes.push_back(i);
        //注意枚举条件 i * primes[j]表示要被筛除的数字(一定不是质数)
        for(int j = 0;j < primes.size() && i * primes[j] <= n; ++j)
        {
            vis[i * primes[j]] = true;
            if(i % primes[j] == 0)
            {
                //说明此时primes[j] 已经不是i * primes[j] 的最小质因子了
                break;
            }
        }
    }
}
唯一分解定理

唯一分解定理指的是:任何一个大于1的自然数都可以唯一地分解为有限个质数的乘积
KaTeX parse error: Can't use function '\(' in math mode at position 3: \̲(̲x = p_1^{k_1}×p…
这个式子中的p1,p2是类似2, 3, 5, 7这样的质数。 将单个数字进行质因数方法是,从小到大枚举x的所有可能的质因子,最大枚举到sqrt(x),每遇到一个可以整除的数字i,就不断进行除法直到除尽。最后如果还有x>1,说明还有一个较大的质因子。

#include 
using namespace std;
const int N = 2e5 + 9;
vector> v;
int main()
{
    int x;cin >> x;
    //枚举所有可能的质因子
    for(int i = 2;i <= x / i; ++ i)
    {
        //如果不能整除直接跳过
        if(x % i)continue;
        //如果可以整除,那么必然是一个质因子(从小到大枚举的特性决定)
        //cnt表示当前这个质因子i的指数
        int cnt = 0;
        //一直除,直到除干净
        while(x % i == 0)cnt ++, x /= i;
        v.push_back({i, cnt});
    }
    if(x > 1)v.push_back({x, 1});
    for(const auto i : v)cout << i.first << ' ' << i.second << '\n';

    return 0;
}
约数个数定理

通过某个数字的唯一分解:
x = p 1 k 1 × p 2 k 2 × . . . × p m k m x = p_1^{k_1}×p_2^{k_2}×...×p_m^{k_m} x=p1k1×p2k2×...×pmkm
我们可以求出x的约数(因数)个数,如果学过线性代数或者有向量相关的知识的话,可以理解为将不同的质因子看作是不同的向量空间或基底,不同质因子之间互不干扰。 也就是说p1的指数的取值是[0, k1]共(k1 + 1)个,p2,p3…亦然,所以x的约数的个数就是
( k 1 + 1 ) ∗ ( k 2 + 1 ) ∗ … ∗ ( k m + 1 ) (k1 + 1)*(k2 + 1)*…*(km + 1) (k1+1)(k2+1)(km+1)
,即:
d ( x ) = ∏ i = 1 m ( k i + 1 ) d(x)=\prod_{i = 1}^{m}(k_i + 1) d(x)=i=1m(ki+1)
例题:定义阶乘 n!=1×2×3×⋅⋅⋅×nn!=1×2×3×⋅⋅⋅×n

请问 100!(100 的阶乘)有多少个正约数。

#include 
using namespace std;
#define int long long
int num[105];

void init(int n){
    for(int i=2;i<=n/i;i++){
        if(n%i)continue;
        while(n%i==0)num[i]++,n/=i;
    }
    if(n>1)num[n]++;
}

signed main()
{
    for(int i=1;i<=100;i++){
        init(i);
    }
    int ans=1;
    for(int i=1;i<=100;i++){
        ans*=(num[i]+1);
    }
    cout<
约数和定理

通过某个数字的唯一分解:
x = p 1 k 1 × p 2 k 2 × . . . × p m k m x = p_{1}^{k_{1}}\times p_{2}^{k_{2}}\times... \times p_{m}^{k_{m}} x=p1k1×p2k2×...×pmkm
我们可以求出x的约数(因数)之和,与约数个数定理类似。p1对于约数和的贡献为1或p1或p12或…或p1k1,于是x的约数之和可以表达为:
s u m ( x ) = ∏ i = 1 m ∑ j = 0 k i p i j sum(x) =\prod_{i = 1}^{m}\sum_{j = 0}^{k_{i}}p_{i}^{j} sum(x)=i=1mj=0kipij
约数和计算公式: f[i] = (p1^0 + p1^1 + p1^2 + p1^3… + p1^c1) * (p2^0 + p2^1 + p2^2 + p23…p2c2) * …

例题:给定你一个正整数 nn,你需要求出 n!n! 的约数之和,结果对 998244353998244353 取模。

n!:n的阶乘,含义为 1×2×3×…×n1×2×3×…×n

输入包含一个正整数 nn

输出 n! 的约数之和,对 998244353998244353 取模。

#include 
using namespace std;
#define MOD 998244353;
#define int long long
int n;
int num[200005];

void init(int m){
    for(int i=2;i<=m/i;++i){
        if(m%i)continue;
        while(m%i==0)m/=i,num[i]++;
    }
    if(m>1)num[m]++;
}

signed main(){
    cin>>n;
    for(int i=2;i<=n;i++){
        init(i);
    }
    int ans=1;
    for(int i=2;i<=n;i++){
        int sum=0,tmp=1;
        if(num[i]==0)continue;
        for(int j=0;j<=num[i];j++){
            sum=(sum+tmp)%MOD;
            tmp=(tmp*i)%MOD;
        }
        ans=(ans*sum)%MOD;
    }
    cout<
快速幂

朴素的计算a的b次方的方法,所需的时间复杂度为O(b),即用一个循环,每次乘一个a,乘b次。 利用倍增的思想,可以将求幂运算的时间复杂度降低为O(logb)。 当b为偶数:
a b = a ( b / 2 ) ∗ a ( b / 2 ) = ( a 2 ) ( b / 2 ) a^b=a^(b/2)*a^(b/2)=(a^2)^(b/2) ab=a(b/2)a(b/2)=(a2)(b/2)
当b为奇数:
a b = a ∗ a ( b / 2 ) ∗ a ( b / 2 ) = a ∗ ( a 2 ) ( b / 2 ) a^b=a*a^(b/2)*a^(b/2)=a*(a^2)^(b/2) ab=aa(b/2)a(b/2)=a(a2)(b/2)
,注意这里b/2向下取整。 于是迭代求解,直到b为0即可。

int qmi(int a, int b, int p)//对p取模
{
    int res = 1;
    while(b)
    {
        if(b&1)res = res * a % p;//b为奇数,乘一个a到答案里
        a = a * a % p;
      	b >>= 1;//底数平方,指数除以2
    }
    return res%p;
}

你可能感兴趣的:(个人笔记,c++,算法,笔记)