板子集合

单调队列

link

#include<bits/stdc++.h>
using namespace std;
int a[2000005]; // 定义数组存储输入数据
int main(){
    int n,k;cin>>n>>k; // 输入n个元素和窗口大小k
    for(int i=1;i<=n;++i){
        cin>>a[i]; // 读入数组元素
    }
    deque<int>q1; // 双端队列,用于求滑动窗口最小值
    for(int i=1;i<=n;++i){
        // 维护队列单调递增:删除队尾比当前元素大的元素
        while(!q1.empty()&&a[q1.back()]>=a[i])q1.pop_back();
        q1.push_back(i); // 当前元素下标入队
        // 删除不在当前窗口内的元素(下标小于等于i-k)
        while(!q1.empty()&&q1.front()<=i-k)q1.pop_front();
        if(i>=k){
            cout<<a[q1.front()]<<' '; // 输出当前窗口最小值
        }
    }
    cout<<'\n';
    deque<int>q; // 双端队列,用于求滑动窗口最大值
    for(int i=1;i<=n;++i){
        // 维护队列单调递减:删除队尾比当前元素小的元素
        while(!q.empty()&&a[q.back()]<=a[i])q.pop_back();
        q.push_back(i); // 当前元素下标入队
        // 删除不在当前窗口内的元素(下标小于等于i-k)
        while(!q.empty()&&q.front()<=i-k)q.pop_front();
        if(i>=k){
            cout<<a[q.front()]<<' '; // 输出当前窗口最大值
        }
    }
}

单调栈

link

#include<bits/stdc++.h>
using namespace std;
int a[3000006]; // 定义全局数组a,用于存储输入的n个整数

// 快速读入函数(用于优化大量数据输入)
inline int read(){
	int x = 0, f = 1; // x存储结果,f存储正负号
	char ch = getchar_unlocked(); // 使用非锁定版本获取字符,速度更快
	while (!isdigit(ch)){ // 跳过非数字字符
		if (ch == '-') 
			f = -1; // 遇到负号标记为负数
		ch = getchar_unlocked();
	}
	while (isdigit(ch)){ // 处理数字部分
		x = (x << 1) + (x << 3) + (ch ^ 48); // 等价于x*10 + (ch-'0'),使用位运算优化
		ch = getchar_unlocked();
	}
	return x * f; // 返回结果乘以符号
}

// 快速输出函数(用于优化大量数据输出)
inline void write(int x){
	if (x < 0) putchar('-'), x = -x; // 处理负数
	if (x > 9) write(x / 10); // 递归输出高位数字
	putchar(x % 10 + '0'); // 输出当前位数字
}

// 定义结构体f,存储元素下标和值
struct f{
    int num; // 元素下标
    int d;   // 元素值
};

int main(){
    stack<f>s;  // 主栈,用于存储结构体f(元素下标和值)
    stack<int>s2; // 辅助栈,用于临时存储结果,最后逆序输出
    int n;n=read(); // 使用快速读入读取n
    for (int i=1;i<=n;++i)a[i]=read(); // 使用快速读入读取数组a
    
    // 从右向左遍历数组
    for (int i=n;i>=1;--i){
        // 维护单调栈:弹出栈顶所有值小于等于当前元素的元素
        while (!s.empty()&&s.top().d<=a[i])s.pop();
        
        // 如果栈不为空,说明找到了右边第一个大于a[i]的元素
        if (!s.empty())s2.push((s.top().num)); // 将找到的元素下标存入结果栈
        else s2.push(0); // 栈为空说明右边没有更大的元素,存入0
        
        // 将当前元素压入栈中
        s.push({i,a[i]});
    }
    
    // 输出结果:由于是逆序存储,需要弹出栈中所有元素
    while (!s2.empty()){
        write(s2.top()); // 使用快速输出
        putchar(' ');    // 输出空格
        s2.pop();        // 弹出已输出元素
    }
    return 0;
}

并查集

link

#include<bits/stdc++.h>
using namespace std;
int f[200005]; // 并查集数组,f[i]表示i的父节点

// 查找x的根节点,带路径压缩优化
int find(int x){
    if (f[x]!=x)f[x]=find(f[x]); // 如果x不是根,递归查找并压缩路径
    return f[x]; // 返回根节点
}

// 合并x和y所在的集合
void join(int x,int y){
    int f1=find(x),f2=find(y); // 找到x和y的根节点
    if (f1!=f2)f[f1]=f2; // 如果不在同一集合,将f1的根指向f2
}

int main(){
    int n,m;
    cin>>n>>m; // 输入元素个数n和操作次数m
    for (int i=1;i<=n;++i){f[i]=i;} // 初始化并查集,每个元素的父节点是自己
    
    while (m--){
        int z;cin>>z; // 输入操作类型
        if (z==1){ // 操作1:合并操作
            int x,y;cin>>x>>y;
            join(x,y); // 合并x和y所在的集合
        }
        else{ // 操作2:查询操作
            int x,y;cin>>x>>y;
            if (find(x)!=find(y))cout<<"N"<<'\n'; // 不在同一集合输出N
            else cout<<"Y"<<'\n'; // 在同一集合输出Y
        }
    }
}

普通线段树

link

**#include<bits/stdc++.h>
using namespace std;
int w[500005*4],a[500005],lzj[500005*4],lzc[500005*4],p; // w:线段树节点值 a:原始数组 lzj:加法懒标记 lzc:乘法懒标记 p:模数


// 上传节点信息
void pushup(int u){
    w[u]=(w[u*2]+w[u*2+1])%p;
}

// 构建线段树
void build(int u,int l,int r){
    lzc[u]=1; // 乘法懒标记初始化为1
    lzj[u]=0; // 加法懒标记初始化为0
    if(l==r){
        w[u]=a[l]%p;
        return;
    }
    int m=(l+r)/2;
    build(u*2,l,m);
    build(u*2+1,m+1,r);
    pushup(u);
}

// 判断[l,r]是否完全在[L,R]内
bool in(int l,int r,int L,int R){
    return (L<=l)&&(r<=R);
}

// 判断[l,r]是否完全在[L,R]外
bool of(int l,int r,int L,int R){
    return (r<L)||(R<l);
}

// 下传懒标记
void pushdown(int u,int l,int r){
    int m=(l+r)/2;
    // 更新左儿子:先乘后加
    w[u*2]=(1LL*w[u*2]*lzc[u]%p+1LL*lzj[u]*(m-l+1)%p)%p;
    lzc[u*2]=1LL*lzc[u*2]*lzc[u]%p;
    lzj[u*2]=(1LL*lzj[u*2]*lzc[u]%p+lzj[u])%p;
    // 更新右儿子:先乘后加
    w[u*2+1]=(1LL*w[u*2+1]*lzc[u]%p+1LL*lzj[u]*(r-m)%p)%p;
    lzc[u*2+1]=1LL*lzc[u*2+1]*lzc[u]%p;
    lzj[u*2+1]=(1LL*lzj[u*2+1]*lzc[u]%p+lzj[u])%p;
    // 重置当前节点懒标记
    lzc[u]=1;
    lzj[u]=0;
}

// 单点修改
void uqd1(int u,int l,int r,int x,int p){
    if(l==r){
        w[u]=x%p;
        return;
    }
    pushdown(u,l,r);
    int m=(l+r)/2;
    if(p<=m)uqd1(u*2,l,m,x,p);
    else uqd1(u*2+1,m+1,r,x,p);
    pushup(u);
}

// 单点查询
int q1(int u,int l,int r,int p){
    if(l==r)return w[u];
    pushdown(u,l,r);
    int m=(l+r)/2;
    if(p<=m)return q1(u*2,l,m,p);
    else return q1(u*2+1,m+1,r,p);
}

// 区间加法
void uqd2(int u,int l,int r,int L,int R,int x){
    if(in(l,r,L,R)){
        w[u]=(w[u]+1LL*(r-l+1)*x%p)%p;
        lzj[u]=(lzj[u]+x)%p;
        return;
    }
    if(of(l,r,L,R))return;
    pushdown(u,l,r);
    int m=(l+r)/2;
    uqd2(u*2,l,m,L,R,x);
    uqd2(u*2+1,m+1,r,L,R,x);
    pushup(u);
}

// 区间乘法
void uqd3(int u,int l,int r,int L,int R,int x){
    if(in(l,r,L,R)){
        w[u]=1LL*w[u]*x%p;
        lzc[u]=1LL*lzc[u]*x%p;
        lzj[u]=1LL*lzj[u]*x%p;
        return;
    }
    if(of(l,r,L,R))return;
    pushdown(u,l,r);
    int m=(l+r)/2;
    uqd3(u*2,l,m,L,R,x);
    uqd3(u*2+1,m+1,r,L,R,x);
    pushup(u);
}

// 区间查询
int q2(int u,int l,int r,int L,int R){
    if(in(l,r,L,R))return w[u];
    if(of(l,r,L,R))return 0;
    pushdown(u,l,r);
    int m=(l+r)/2;
    return (q2(u*2,l,m,L,R)+q2(u*2+1,m+1,r,L,R))%p;
}

int main(){
    int n,q,m;
    cin>>n>>q>>m;
    p=m; // 设置模数
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }
    build(1,1,n); // 构建线段树
    while(q--){
        int op,x,y,k;
        cin>>op;
        if(op==1){ // 区间乘法
            cin>>x>>y>>k;
            uqd3(1,1,n,x,y,k);
        }else if(op==2){ // 区间加法
            cin>>x>>y>>k;
            uqd2(1,1,n,x,y,k);
        }else if(op==3){ // 区间查询
            cin>>x>>y;
            cout<<q2(1,1,n,x,y)<<endl;
        }
    }
}

朴素 BIT

link

#include<bits/stdc++.h>
using namespace std;
int a[500005],c[500005],n; // a:原始数组 c:树状数组 n:数组大小

// 计算lowbit,获取x二进制表示中最低位的1
inline int lowbit(int x){
    return x&(-x);
}

// 树状数组单点更新:在位置a加上值b
inline void add(int a,int b){
    for (int i=a;i<=n;i+=lowbit(i))c[i]+=b; // 向上更新所有相关节点
}

// 树状数组前缀和查询:求[1,i]区间和
inline int sum(int i){
    int cnt=0;
    for (int j=i;j>=1;j-=lowbit(j))cnt+=c[j]; // 向下累加所有相关节点
    return cnt;
}

int main(){
    int m;
    cin>>n>>m; // 输入数组大小n和操作次数m
    for (int i=1;i<=n;++i){
        cin>>a[i]; // 读入原始数组
        add(i,a[i]); // 初始化树状数组
    }
    while (m--){
        int op;cin>>op; // 输入操作类型
        if (op==1){ // 操作1:单点修改
            int x,k;cin>>x>>k;
            add(x,k); // 在位置x加上k
        }
        else { // 操作2:区间查询
            int l,r;cin>>l>>r;
            cout<<sum(r)-sum(l-1)<<'\n'; // 输出[l,r]区间和
        }
    }
}

朴素 Trie

link

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3000005;
int trie[MAXN][62];  // Trie树节点数组,每个节点有62个子节点(数字+大小写字母)
int cnt[MAXN];       // 记录以每个节点为结尾的字符串数量
int tot;             // Trie树节点计数器

// 字符映射函数:将字符转换为0-61的索引
int charid(char c){
    if (c>='0'&&c<='9')return c-'0';        // 数字0-9映射到0-9
    if (c>='A'&&c<='Z')return c-'A'+10;     // 大写字母映射到10-35
    if (c>='a'&&c<='z')return c-'a'+36;     // 小写字母映射到36-61
}

// 向Trie树中插入字符串
void insert(string s){
    int u=0;  // 从根节点开始
    for (int i=0;i<(int)s.length();++i){
        int a=charid(s[i]);  // 获取当前字符的索引
        if(trie[u][a]==0)trie[u][a]=++tot;  // 如果子节点不存在则创建新节点
        u=trie[u][a];        // 移动到子节点
        cnt[u]++;            // 增加经过该节点的字符串计数
    }
}

// 查询字符串在Trie树中的出现次数
int uqd(string s){
    int u=0;  // 从根节点开始
    for (int i=0;i<(int)s.length();++i){
        int a=charid(s[i]);  // 获取当前字符的索引
        if(trie[u][a]==0)return 0;  // 如果路径不存在则返回0
        u=trie[u][a];        // 移动到子节点
    }
    return cnt[u];  // 返回以该节点为结尾的字符串数量
}

// 处理每组测试数据
void solve(){
    tot=0;  // 重置节点计数器
    int n,q;cin>>n>>q;
    for (int i=1;i<=n;++i){
        string s;cin>>s;
        insert(s);  // 插入所有字符串
    }
    for (int i=1;i<=q;++i){
        string s;cin>>s;
        cout<<uqd(s)<<'\n';  // 查询每个字符串
    }
    // 清空Trie树,为下一组测试数据做准备
    for(int i=0;i<=tot;i++){
        memset(trie[i],0,sizeof(trie[i]));
        cnt[i]=0;
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;cin>>T;
    while (T--)solve();  // 处理T组测试数据
}

0/1 Trie

link

#include<bits/stdc++.h>
using namespace std;
const int N=5000005;
int trie[N][2],a[1000005],tot;  // trie:01字典树 a:输入数组 tot:节点计数
// 向01字典树中插入整数x
void insert(int x){
    int p=0;  // 从根节点开始
    for (int i=(1<<30);i;i>>=1){  // 从最高位(第30位)到最低位遍历
        int a=(x&i)?1:0;  // 提取当前位的值(0或1)
        if (!trie[p][a])trie[p][a]=++tot;  // 如果路径不存在则创建新节点
        p=trie[p][a];  // 移动到子节点
    }
}
// 查询与x进行异或能得到的最大值
int query(int x){
    int p=0,cnt=0;  // p:当前节点 cnt:累计的异或值
    for (int i=(1<<30);i;i>>=1){  // 从最高位到最低位遍历
        int a=(x&i)?1:0;  // 提取x当前位的值
        if (trie[p][a^1]){  // 优先选择与当前位相反的路径(使异或结果最大)
            cnt+=i;  // 当前位异或结果为1,累加到结果中
            p=trie[p][a^1];  // 移动到相反分支
        }
        else p=trie[p][a];  // 相反分支不存在,只能走相同分支
    }
    return cnt;  // 返回最大异或值
}
int main(){
    int n;cin>>n;
    for (int i=1;i<=n;++i){
        cin>>a[i];
        insert(a[i]);  // 将所有数字插入01字典树
    }
    int maxn=0;
    for (int i=1;i<=n;++i)maxn=max(maxn,query(a[i]));  // 查询每个数字能得到的最大异或值
    cout<<maxn;  // 输出所有最大异或值中的最大值
}

手写堆

link

#include<bits/stdc++.h>
#define int long long
using namespace std;
int w[1000005],cnt=0;  // w:堆数组 cnt:堆中元素个数

// 获取堆顶元素(最小值)
int top(){
    return w[1];
}

// 插入后的上浮调整(维护小顶堆性质)
void modify(int u){// 插入后的修复
    if (u==1)return;  // 到达根节点,停止
    if (w[u]>w[u/2])return;  // 当前节点值大于父节点,满足小顶堆性质,停止
    swap(w[u],w[u/2]);  // 当前节点值小于父节点,交换
    modify(u/2);  // 递归向上调整
}

// 向堆中插入元素
void push(int x){// 插入
    w[++cnt]=x;  // 将新元素放到堆的末尾
    modify(cnt);  // 从新元素位置开始向上调整
}

// 删除后的下沉调整(维护小顶堆性质)
void repair(int u){// 删除后的修复
    int t = u;  // t记录最小值的节点下标
    // 与左子节点比较
    if (u*2<=cnt && w[u*2]<w[t]) t = u*2;
    // 与右子节点比较
    if (u*2+1<=cnt && w[u*2+1]<w[t]) t = u*2+1;
    // 如果当前节点不是最小值,需要交换并继续调整
    if (u!=t){
        swap(w[u],w[t]);
        repair(t);  // 递归向下调整
    }
}

// 删除堆顶元素
void pop(){// 删除
    swap(w[1],w[cnt]);// 交换根节点和最后一个节点
    w[cnt]=0;cnt--;// 删除最后一个节点(原堆顶)
    repair(1);  // 从根节点开始向下调整
}

signed main(){
    int q;cin>>q;  // 操作次数
    while (q--){
        int op;cin>>op;  // 操作类型
        if (op==1){  // 插入操作
            int x;cin>>x;
            push(x);
        }
        if (op==2){  // 查询堆顶操作
            cout<<top()<<'\n';
        }
        if (op==3){  // 删除堆顶操作
            pop();
        }
    }
}

强联通(缩点)

link

#include<bits/stdc++.h>
using namespace std;
struct edge{
    int nxt,to;  //nxt存储下一条边的索引,to存储这条边指向的节点
}e[200005];
int hd[10005],tot;  //hd数组是头指针数组,hd[u]表示节点u的第一条边在e数组中的索引,tot是边的计数器
void add(int u,int v){
    e[++tot].to=v;         //新边的终点是v
    e[tot].nxt=hd[u];      //新边的下一条边是原来u的第一条边
    hd[u]=tot;             //更新u的第一条边为新边
}
stack<int>sk;              //Tarjan算法使用的栈
int scc[10005],sc,dfnc,dfn[10005],sz[10005],low[10005],vis[10005],w[10005],a[10005];
//scc[i]表示节点i属于哪个强连通分量,sc是强连通分量计数器
//dfnc是DFS序计数器,dfn[i]是节点i的DFS序编号
//sz[i]表示第i个强连通分量包含的节点数
//low[i]表示节点i能回溯到的最早的DFS序
//vis[i]标记节点i是否在栈中
//w[i]表示第i个强连通分量的总权值
//a[i]表示原图中节点i的权值
void tarjan(int u){
    low[u]=dfn[u]=++dfnc,sk.push(u),vis[u]=1;  //初始化low和dfn为DFS序,节点入栈,标记在栈中
    for(int i=hd[u];i!=0;i=e[i].nxt){          //遍历u的所有出边
        int v=e[i].to;
        if(!dfn[v]){                           //如果v节点还没有被访问过
            tarjan(v);                         //递归处理v节点
            low[u]=min(low[u],low[v]);         //用v的low值更新u的low值
        }
        else if(vis[v]){                       //如果v节点已经被访问过且在栈中
            low[u]=min(low[u],dfn[v]);         //用v的dfn值更新u的low值
        }
    }
    if(dfn[u]==low[u]){                        //如果u是强连通分量的根节点
        sc++;                                  //强连通分量计数器加1
        while(1){
            int v=sk.top();sk.pop();           //弹出栈顶节点
            scc[v]=sc,sz[sc]++,vis[v]=0;       //标记v属于第sc个强连通分量,该分量节点数加1,标记v不在栈中
            if(v==u)break;                     //直到弹出u节点为止
        }
    }
}
edge e2[100005];           //新的边数组,用于存储缩点后的DAG
int hd2[10005],tot2,in[10005];  //hd2是新图的头指针,tot2是新图的边计数器,in记录每个强连通分量的入度
void add2(int u,int v){
    e2[++tot2].to=v;
    e2[tot2].nxt=hd2[u];
    hd2[u]=tot2;
}
int ok[10005],f[10005];    //ok标记节点是否被访问过,f[i]表示从第i个强连通分量出发能获得的最大权值和
void dfs(int u){
    f[u]=w[u];ok[u]=1;     //初始化f[u]为当前强连通分量的权值,标记已访问
    for(int i=hd2[u];i!=0;i=e2[i].nxt){  //遍历u的所有出边
        int v=e2[i].to;
        if(!ok[v]){
            dfs(v);                      //递归处理v
        }
        f[u]=max(f[u],w[u]+f[v]);       //更新从u出发能获得的最大权值和
    }
}
int main(){
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;++i)cin>>a[i];     //读入每个节点的权值
    for(int i=1;i<=m;++i){
        int u,v;cin>>u>>v;
        add(u,v);                       //建立原图的有向边
    }
    for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i);  //对每个未访问的节点执行Tarjan算法求强连通分量
    for(int u=1;u<=n;++u){
        for(int i=hd[u];i!=0;i=e[i].nxt){
            int v=e[i].to;
            if(scc[u]!=scc[v])add2(scc[v],scc[u]),in[scc[u]]++;  //建立缩点后的DAG,注意方向反转
        }
    }
    for(int i=1;i<=n;++i){
        w[scc[i]]+=a[i];                //计算每个强连通分量的总权值
    }
    for(int i=1;i<=sc;++i){
        if(!in[i]){                     //从入度为0的强连通分量开始DFS
            dfs(i);
        }
    }
    int maxn=0;
    for(int i=1;i<=sc;++i)maxn=max(maxn,f[i]);  //找出所有强连通分量中的最大权值和
    cout<<maxn;
}

KMP

link

#include<bits/stdc++.h>
using namespace std;
int b[2000005];  //KMP算法的部分匹配表(next数组)
int main() {
	string s1,s2;cin>>s1>>s2;  //s1是主串,s2是模式串
	int j=0;
	if (s2.length()>0) {
		b[0]=0;  //部分匹配表的第一个元素总是0
		//构建模式串s2的部分匹配表
		for (int i=1;i<s2.length();++i){
			//当字符不匹配时,利用已计算的部分匹配值回溯
			while (j>0&&s2[i]!=s2[j]){
				j=b[j-1];
			}
			//如果字符匹配,增加匹配长度
			if (s2[i]==s2[j])j++;
			b[i]=j;  //记录当前位置的部分匹配值
		}
	}
	j=0;  //重置匹配长度,用于主串匹配
	//在主串s1中搜索模式串s2
	for (int i=0;i<s1.length();++i){
		//当字符不匹配时,利用部分匹配表回溯
		while (j>0&&s1[i]!=s2[j])j=b[j-1];
		//如果字符匹配,增加匹配长度
		if (s2[j]==s1[i])j++;
		//找到完整匹配
		if (j==s2.length()){
			//输出匹配起始位置(从1开始计数)
			cout<<(i+1)-s2.length()+1<<'\n';
			//继续搜索下一个可能的匹配
			j=b[j-1];
		}
	}
    //输出模式串的部分匹配表
    for (int i=0;i<s2.length();++i)cout<<b[i]<<" ";
}

manacher

link

#include <bits/stdc++.h>
using namespace std;
const int N=2.2e7+5;
int r[N],m,n,ans=1;  //r[i]表示以i为中心的最长回文半径,m是原串长度,n是新串长度,ans记录最长回文子串长度
char s[N],t[N];      //s是原字符串,t是预处理后的新字符串
int main() {
    cin>>s+1,m=strlen(s+1);  //从下标1开始读入字符串s,并计算长度m
    t[0]='#',t[++n]='#';     //新字符串t开头添加特殊字符'#'
    for (int i=1;i<=m;++i)t[++n]=s[i],t[++n]='#';  //将原字符串每个字符间插入'#'
    t[++n]='$',r[1]=1;       //新字符串结尾添加'$',初始化r[1]=1
    //Manacher算法核心部分
    for (int i=2,c=1,R=1;i<n;++i){  //c表示当前回文中心,R表示当前回文右边界
        r[i]=min(R-i+1,r[2*c-i]);   //利用对称性初始化r[i]
        while (t[i-r[i]]==t[i+r[i]])r[i]++;  //中心扩展法
        if (i+r[i]-1>R)c=i,R=i+r[i]-1;       //更新回文中心和右边界
        ans=max(ans,r[i]-1);         //更新最长回文子串长度(r[i]-1即为原串中对应回文长度)
    }
    cout<<ans;  //输出最长回文子串长度
    return 0;
}

Dij

link

#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
struct edge{
    int nxt,to,w;  //nxt:下一条边的索引 to:边的终点 w:边的权重
}e[2000005];
int hd[1000005],tot;  //hd:头指针数组 tot:边计数器
void add(int u,int v,int w){  //添加从u到v权重为w的有向边
    e[++tot].to=v;
    e[tot].nxt=hd[u];
    e[tot].w=w;
    hd[u]=tot;
}
int dis[1000005];  //dis[i]表示从源点s到i的最短距离
priority_queue<pii,vector<pii>,greater<pii>> q;  //小顶堆,存储(距离,节点)
int main(){
    int n,m,s;cin>>n>>m>>s;  //n:节点数 m:边数 s:源点
    for (int i=1;i<=m;++i){ 
        int u,v,w;cin>>u>>v>>w;
        add(u,v,w);  //建图
    }
    memset(dis,0x3f,sizeof dis);  //初始化所有距离为无穷大
    dis[s]=0;  //源点到自己的距离为0
    q.push(make_pair(0,s));  //将源点加入优先队列
    while (!q.empty()){
        auto t=q.top();q.pop();  //取出当前距离最小的节点
        int u=t.second;
        if (t.first!=dis[u])continue;  //如果该节点的距离已被更新过,跳过
        for (int i=hd[u];i!=0;i=e[i].nxt){  //遍历u的所有出边
            int v=e[i].to,d=e[i].w+t.first;  //计算经过u到v的新距离
            if (d<dis[v]){  //如果找到更短的路径
                dis[v]=d;  //更新距离
                q.push(make_pair(d,v));  //将新距离加入优先队列
            }
        }
    }
    for (int i=1;i<=n;++i)cout<<dis[i]<<" ";  //输出从源点到所有节点的最短距离
}

割点

判定:对于某个顶点 \(u\),如果存在至少一个顶点 \(v\)\(u\) 的儿子),使得 \(low_v \geq dfn_u\),即不能回到祖先,那么 \(u\) 点为割点。

link

#include<bits/stdc++.h>
using namespace std;
stack<int>sk;  //Tarjan算法使用的栈
struct edge{
    int to,nxt;  //to:边的终点 nxt:下一条边的索引
}e[400005];
int hd[200005],cnt;  //hd:头指针数组 cnt:边计数器
int dfn[200005],dfnc,low[200005],vis[200005];  //dfn:DFS序 low:能回溯到的最早DFS序 vis:是否在栈中
set<int>st;  //存储割点集合(自动去重)
int rt;  //当前DFS树的根节点

void add(int u,int v){  //添加无向边
    e[++cnt]={v,hd[u]};
    hd[u]=cnt;
}

void tarjan(int u){  //Tarjan算法求割点
    low[u]=dfn[u]=++dfnc;  //初始化low和dfn
    sk.push(u);  //节点入栈
    vis[u]=1;  //标记在栈中
    int num=0;  //记录u的子树数量
    
    for(int i=hd[u];i!=0;i=e[i].nxt){  //遍历u的所有邻接点
        int v=e[i].to;
        if(!dfn[v]){  //如果v未被访问
            tarjan(v);  //递归处理v
            low[u]=min(low[u],low[v]);  //更新u的low值
            if(low[v]>=dfn[u]){  //如果v不能绕过u到达更早的节点
                if(u!=rt){  //且u不是根节点
                    st.insert(u);  //u是割点
                }
                num++;  //子树数量增加
            }
        }
        else if(vis[v]){  //如果v已被访问且在栈中(后向边)
            low[u]=min(low[u],dfn[v]);  //更新u的low值
        }
    }
    if(u==rt&&num>=2){  //如果是根节点且有至少2棵子树
        st.insert(u);  //根节点是割点
    }
}
int main(){
    int n,m;cin>>n>>m;  //n:节点数 m:边数
    for(int i=1;i<=m;++i){
        int u,v;cin>>u>>v;
        add(u,v),add(v,u);  //建立无向图
    }
    
    for(int i=1;i<=n;++i){  //遍历所有节点
        if(!dfn[i]){  //如果节点i未被访问
            rt=i;  //设置当前DFS树的根节点
            tarjan(i);  //从i开始Tarjan算法
        }
    }
    
    cout<<st.size()<<'\n';  //输出割点数量
    for(auto i:st)cout<<i<<' ';  //输出所有割点
}

LCA

e,给个核心得了

//初始化ST表,进行倍增预处理
void init(){
    for(int j=1;j<=19;++j){ //j从1到19,表示2^j级祖先
        for(int i=1;i<=n;++i){ //遍历每个节点
            f[i][j]=f[f[i][j-1]][j-1]; //节点i的2^j级祖先是其2^(j-1)级祖先的2^(j-1)级祖先
        }
    }
}

//求节点u和v的最近公共祖先(LCA)
int lca(int u,int v){
    //保证u的深度不小于v的深度
    if(dep[u]<dep[v])swap(u,v);
    //将u提升到与v同一深度
    for(int j=19;j>=0;--j){ //从大到小尝试跳跃
        if(dep[f[u][j]]>=dep[v])u=f[u][j]; //如果跳跃后深度仍不小于v,则进行跳跃
    }
    //如果此时u和v相同,说明v就是u的祖先,直接返回
    if(u==v)return u;
    //同时提升u和v,保持它们在同一深度但祖先不同
    for(int j=19;j>=0;--j){ //从大到小尝试跳跃
        if(f[u][j]!=f[v][j]){ //如果跳跃后祖先不同,说明还没到LCA,可以跳跃
            u=f[u][j];
            v=f[v][j];
        }
    }
    //此时u和v的父节点就是LCA
    return f[u][0];
}

exgcd

//扩展欧几里得算法:求解ax + by = gcd(a,b)的一组整数解(x,y)
void exgcd(int a,int b,int& x,int& y){
    if(b==0){ //递归终止条件:b为0时,gcd(a,b)=a
        x=1,y=0;return; //此时方程的解为x=1,y=0(a*1 + 0*0 = a)
    }
    exgcd(b,a%b,x,y); //递归求解,参数变为(b, a%b)
    int _y=x-(a/b)*y; //利用递归结果计算当前层的y值
    x=y,y=_y; //更新x和y为当前层的解
}

费马小定理

对于任意互质 \(a,p\),有 \(a^{p-1}≡ 1 \pmod p\)

扩展欧拉定理

其中 \(\phi(n)\) 为欧拉函数。

更一般地,对于任意整数 \(a,m,m \geq 1\) 和 $b \geq \phi(m) $:

\[a^b \equiv a^{b \bmod \phi(m) + \phi(m)} \pmod{m} \]

(当 \(\gcd(a,m)=1\) 时,可去掉 $ +\phi(m) $ 项,即退化为欧拉定理形式。)

\(phi(n)\) 求法(一定必背的!!!):

//欧拉函数:计算小于等于n且与n互质的正整数的个数
int oula(int n){
    int ans=n; //初始化答案为n
    for(int i=2;i*i<=n;++i){ //遍历所有可能的质因子i(从2到sqrt(n))
        if(n%i==0){ //如果i是n的质因子
            ans-=ans/i; //根据欧拉函数公式:ans = ans * (1 - 1/i)
            while(n%i==0)n/=i; //将n中的所有i因子去除
        }
    }
    if(n>1)ans-=ans/n; //如果n还有大于sqrt(n)的质因子,同样处理
    return ans; //返回欧拉函数值
}

exCRT

link

#include<bits/stdc++.h>
#define int __int128  //使用128位整数
using namespace std;
//快速读入函数(优化输入)
inline int read(){
	int x=0,f=1;
	char ch=getchar_unlocked();
	while(!isdigit(ch)){
		if(ch=='-') 
			f=-1;
		ch=getchar_unlocked();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar_unlocked();
	}
	return x*f;
}
//快速输出函数(优化输出)
inline void write(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
//扩展欧几里得算法:求解ax+by=gcd(a,b)
void exgcd(int a,int b,int &x,int &y){
    if(!b){x=1,y=0;return;}
    exgcd(b,a%b,y,x);y-=a/b*x;
}
signed main(){
    int n,a1,b1,a2,b2,x,y;n=read(),a1=read(),b1=read();  //读入n和第一个方程的参数
    for(int i=2;i<=n;++i){  //依次处理每个方程
        a2=read(),b2=read();  //读入当前方程的参数
        int d=__gcd(a1,a2),c=(b2-b1)%a2;  //d是a1,a2的最大公约数,c是常数项差
        if(c%d){cout<<-1;return 0;}  //如果c不能被d整除,无解
        exgcd(a1,a2,x,y);  //求解a1*x+a2*y=d
        int lcm=a1/d*a2;  //计算a1和a2的最小公倍数
        //更新解:b1 = 当前解 + k*lcm,取最小非负解
        b1=((1ll*x*(c/d)%a2*a1+b1)%lcm+lcm)%lcm;
        a1=lcm;  //更新模数为新的最小公倍数
    }
    write(b1);  //输出最终解
}

高斯消元

给定一个线性方程组,对其求解。

\[\begin{cases} a_{1, 1} x_1 + a_{1, 2} x_2 + \cdots + a_{1, n} x_n = b_1 \\ a_{2, 1} x_1 + a_{2, 2} x_2 + \cdots + a_{2, n} x_n = b_2 \\ \cdots \\ a_{n,1} x_1 + a_{n, 2} x_2 + \cdots + a_{n, n} x_n = b_n \end{cases} \]

link

#include<bits/stdc++.h>
using namespace std;
double a[105][105];  //增广矩阵,a[i][n+1]存储常数项
const double eps=0.0000001;  //精度阈值,用于判断浮点数是否为0
int n; 
//高斯消元法求解线性方程组,返回是否有解
bool guess(){ 
    for(int i=1;i<=n;++i){  //遍历每一列
        //选主元:在当前列i中从第i行开始往下找第一个非零元素
        int r=i;
        for(int k=i;k<=n;++k){
            if(fabs(a[k][i])>eps){  //找到第一个非零元素
                r=k;
                break;
            }
        }
        if(r!=i)swap(a[i],a[r]);  //将找到的行与当前行交换
        if(fabs(a[i][i])<eps)return 0;  //如果主元为0,说明无解或有无穷多解
        //将主元化为1,同时对该行其他元素进行相应操作
        for(int j=n+1;j>=i;--j){  //从右往左处理,避免提前覆盖主元
            a[i][j]/=a[i][i];
        }
        //消元:用当前行消去下面各行在第i列的元素
        for(int k=i+1;k<=n;++k){
            for(int j=n+1;j>=i;--j){  //从右往左处理
                a[k][j]-=a[i][j]*a[k][i];
            }
        }
    }
    //回代求解
    for(int i=n-1;i>=1;--i){  //从最后一行往前回代
        for(int j=i+1;j<=n;++j){
            a[i][n+1]-=a[i][j]*a[j][n+1];  //减去已知变量的贡献
        }
    }
    return 1;  //求解成功
}
int main(){
    cin>>n;  //输入方程个数(未知数个数)
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n+1;++j){
            cin>>a[i][j];  //读入增广矩阵
        }
    }
    if(!guess())cout<<"No Solution";  //无解
    else {
        for(int i=1;i<=n;++i){
            cout<<fixed<<setprecision(2)<<a[i][n+1]<<'\n';  //输出解,保留2位小数
        }
    }
}

矩阵加速

link

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
struct mar{
    int a[5][5];  //3x3矩阵结构体
};
//矩阵乘法
mar mul(mar x,mar y){
    mar c;
    memset(c.a,0,sizeof(c.a));  //初始化结果矩阵为0
    for(int i=1;i<=3;++i){
        for(int j=1;j<=3;++j){
            for(int k=1;k<=3;++k){
                c.a[i][j]=(c.a[i][j]+x.a[i][k]*y.a[k][j]%mod)%mod;  //矩阵乘法并取模
            }
        }
    }
    return c;
}
//矩阵快速幂
mar qpow(mar mart,int b){
    mar res;
    memset(res.a,0,sizeof(res.a));
    for(int i=1;i<=3;++i)res.a[i][i]=1;  //初始化为单位矩阵
    while(b){
        if(b&1)res=mul(res,mart);  //如果当前二进制位为1,乘上对应矩阵
        mart=mul(mart,mart);       //矩阵平方
        b>>=1;                     //指数右移
    }
    return res;
}
signed main(){
    int T;cin>>T;  //测试数据组数
    while(T--){
        int n;cin>>n;
        if(n<=3){cout<<"1\n";continue;}  //前3项直接输出1
        //构造递推矩阵:[f(n), f(n-1), f(n-2)] = [f(n-1), f(n-2), f(n-3)] * M
        //递推关系:f(n) = f(n-1) + f(n-3)
        mar cnt;
        cnt.a[1][1]=1,cnt.a[1][2]=1,cnt.a[1][3]=0;  //第一行:f(n)=1*f(n-1)+1*f(n-2)+0*f(n-3)
        cnt.a[2][1]=0,cnt.a[2][2]=0,cnt.a[2][3]=1;  //第二行:f(n-1)=0*f(n-1)+0*f(n-2)+1*f(n-3)
        cnt.a[3][1]=1,cnt.a[3][2]=0,cnt.a[3][3]=0;  //第三行:f(n-2)=1*f(n-1)+0*f(n-2)+0*f(n-3)
        mar res=qpow(cnt,n-3);  //计算矩阵的(n-3)次幂
        //初始状态:[f(3), f(2), f(1)] = [1, 1, 1]
        //结果 = 1*f(3) + 1*f(2) + 1*f(1) = res.a[1][1]*1 + res.a[1][2]*1 + res.a[1][3]*1
        cout<<(res.a[1][1]+res.a[1][2]+res.a[1][3])%mod<<'\n';
    }
}

图上/树上 矩阵加速

一样的思想,只不过初始矩阵变成了图/树的邻接矩阵。

posted @ 2025-10-25 13:19  Cefgskol  阅读(151)  评论(0)    收藏  举报