算法竞赛模板整理
基础算法
快速排序
快速排序
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
void quick_sort(int a[],int l,int r){
if(l>=r)
return;
int x = a[l+r>>1], i = l - 1, j = r + 1;
while (i<j)
{
// while(a[++i]<x);
// while(a[--j]>x);
do i++; while(a[i]<x);
do j--; while(a[j]>x);
if(i<j)
swap(a[i],a[j]);
}
quick_sort(a,l,j);
quick_sort(a,j+1,r);
}
int n;
int a[maxn];
int main()
{
scanf("%d",&n);
for (int i = 1;i<=n;i++){
scanf("%d",&a[i]);
}
quick_sort(a, 1, n);
for (int i = 1;i<=n;i++){
printf("%d ",a[i]);
}
return 0;
}
快速选择算法—第k个数
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn];
int n,k;
int quick_sort(int l,int r,int k){
if(l>=r) return a[l];
int x=a[l+r>>1],i=l-1,j=r+1;
while(i<j){
while(a[++i]<x);
while(a[--j]>x);
if(i<j) swap(a[i],a[j]);
}
int sl=j-l+1;
if(k<=sl) return quick_sort(l,j,k);
else return quick_sort(j+1,r,k-sl);
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
cout<<quick_sort(1,n,k)<<"\n";
}
归并排序
归并排序
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn],tmp[maxn];
void merge_sort(int a[],int l,int r){
if(r<=l) return ;
int mid=l+r>>1;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r){
if(a[i]<=a[j])tmp[++k]=a[i++];
else tmp[++k]=a[j++];
}
while(i<=mid) tmp[++k]=a[i++];
while(j<=r) tmp[++k]=a[j++];
for(i=l,j=1;i<=r;i++,j++) a[i]=tmp[j];
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
merge_sort(a,1,n);
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
}
逆序对数量
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
int a[maxn],tmp[maxn];
int n;
ll merge_sort(int l,int r){
if(l>=r) return 0;
int mid=l+r>>1;
ll res=0;
res+=merge_sort(l,mid);
res+=merge_sort(mid+1,r);
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r){
if(a[i]<=a[j]){
tmp[++k]=a[i++];
}
else{
tmp[++k]=a[j++];
res+=mid-i+1;
}
}
while(i<=mid) tmp[++k]=a[i++];
while(j<=r) tmp[++k]=a[j++];
for(i=l,j=1;i<=r;i++) a[i]=tmp[j++];
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
cout<<merge_sort(1,n)<<"\n";
}
二分
模板
// 区间[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;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
数的范围
对于每个查询,返回一个元素k的起始位置和终止位置
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn];
int n,q;
int main() {
scanf("%d%d",&n,&q);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
}
while(q--) {
int x;
scanf("%d",&x);
// if(a[lower_bound(a+1,a+n+1,x)-a]==x) {
// cout<<lower_bound(a+1,a+n+1,x)-a-1<<" "<<upper_bound(a+1,a+n+1,x)-a-2<<"\n";
// }
// else {
// cout<<"-1 -1\n";
// }
int l=0,r=n;
while(l<r) {
int mid=l+r>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
if(a[l]!=x) {
cout<<"-1 -1\n";
} else {
cout<<l-1<<" ";
l=0,r=n;
while(l<r) {
int mid=l+r+1>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l-1<<"\n";
}
}
}
数的三次方根
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int main(){
double n;
scanf("%lf",&n);
//printf("%lf\n",cbrt(n));
double sign=n>0?1:-1;
n=fabs(n);
double l=0,r=n;
while(r-l>1e-8){
double mid=(l+r)/2;
if(mid*mid*mid<n) l=mid;
else r=mid;
}
printf("%lf\n",l*sign);
}
高精度
高精度加法
#include<bits/stdc++.h>
using namespace std;
vector<int> add(vector<int> &A,vector<int> &B){
vector<int>C;
int t=0;
for(int i=0;i<A.size()||i<B.size();i++){
if(i<A.size()) t+=A[i];
if(i<B.size()) t+=B[i];
C.push_back(t%10);
if(t<10) t=0;
else t=1;
}
if(t) C.push_back(t);
return C;
}
int main(){
string a,b;
cin>>a>>b;
vector<int>A,B;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
auto C=add(A,B);
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
}
高精度减法
#include<bits/stdc++.h>
using namespace std;
vector<int> sub(vector<int> &A,vector<int> &B){
vector<int>C;
int t=0;
for(int i=0;i<A.size();i++){
t=A[i]-t;
if(i<B.size()) t-=B[i];
C.push_back((t+10)%10);
if(t<0) t=1;
else t=0;
}
while(C.size()>1&&C.back()==0) C.pop_back();
return C;
}
string a,b;
int main(){
int sign=0;
cin>>a>>b;
if(a.size()<b.size()||a.size()==b.size()&&a<b) swap(a,b),sign=1;
vector<int>A,B;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
auto C=sub(A,B);
printf(sign?"-":"");
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
return 0;
}
高精度乘法
#include<bits/stdc++.h>
using namespace std;
vector<int> mul(vector<int> &A,int b){
vector<int>C;
int t=0;
for(int i=0;i<A.size();i++){
t=t+A[i]*b;
C.push_back(t%10);
t=t/10;
}
while(t)C.push_back(t%10),t/=10;
return C;
}
string a;
int b;
int main(){
cin>>a>>b;
vector<int>A;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
auto C=mul(A,b);
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
return 0;
}
高精度除法
#include<bits/stdc++.h>
using namespace std;
vector<int> div(vector<int> &A,int b,int &r){
vector<int>C;
r=0;
for(int i=A.size()-1;i>=0;i--){
r=r*10+A[i];
C.push_back(r/b);
r%=b;
}
reverse(C.begin(),C.end());
while(C.size()>1&&C.back()==0) C.pop_back();
return C;
}
string a;
int b,r;
int main(){
cin>>a>>b;
vector<int>A;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
auto C=div(A,b,r);
for(int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
cout<<"\n"<<r;
return 0;
}
前缀和和差分
一维前缀和
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
二维前缀和
int get_sum(int x1,int y1,int x2,int y2){
return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
一维差分
void insert(int l,int r,int k){
d[l]+=k,d[r+1]-=k;
}
二维差分
void insert(int x1,int y1,int x2,int y2,int k){
d[x1][y1]+=k;
d[x2+1][y1]-=k;
d[x1][y2+1]-=k;
d[x2+1][y2+1]+=k;
}
位运算
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
2进制下1的个数:__builtin_popcount(n);
离散化
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
int get_id(int x){
return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1; //二分查询离散值
}
区间合并
将所有存在交集的区间合并
void merge(vector<pii> &segs) {
vector<pii>res;
sort(segs.begin(),segs.end());
int st=-2e9,en=-2e9;
for(auto it:segs) {
if(it.first>en) {
if(st!=-2e9) res.push_back({st,en});
st=it.first,en=it.second;
} else en=max(en,it.second);
}
if(st!=-2e9) res.push_back({st,en});
segs=res;
}
写在后面
1.函数传引用避免拷贝时间
2.__builtin_popcount()很香
3.快排,归并,二分注意处理边界问题
数据结构
单链表
//单链表
int head,e[maxn],ne[maxn],idx=0;
int n;
//头结点最开始指向0,插入第一个值idx=1;
void add_head(int x){
e[++idx]=x;
ne[idx]=head;
head=idx;
}
void remove(int k){
ne[k]=ne[ne[k]];
}
void add(int k,int x){
e[++idx]=x;
ne[idx]=ne[k];
ne[k]=idx;
}
for(int i=head;i;i=ne[i]){
printf("%d ",e[i]);
}
双链表
int e[maxn],ne[maxn],pr[maxn],idx=1;
int n;
// e[]表示节点的值,pr[]表示节点的左指针,ne[]表示节点的右指针,idx表示当前用到了哪个节点
void init(){
//0是左端点,1是右端点
ne[0]=1;
pr[1]=0;
}
// 在节点k的右边插入一个数x
void add(int k,int x){
e[++idx]=x;
ne[idx]=ne[k];
pr[idx]=k;
pr[ne[k]]=idx;
ne[k]=idx;
}
// 删除节点k
void remove(int k){
ne[pr[k]]=ne[k];
pr[ne[k]]=pr[k];
}
栈
// tt表示栈顶
int st[maxn], tt = 0;
// 向栈顶插入一个数
st[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
st[tt];
// 判断栈是否为空
if (tt > 0) {
}
队列
// hh 表示队头,tt表示队尾
int qu[maxn],hh=1,tt=0;
// 向队尾插入一个数
qu[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
qu[hh];
// 判断队列是否为空
if (hh <= tt) {
}
单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ ){
while(tt&&check(stk[tt])) tt--;
stk[++tt]=i or a[i];
}
单调队列
常见模型:找出滑动窗口中的最大值/最小值
int hh = 1, tt = 0;
for (int i = 1; i <=n; i ++ ) {
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt],i)) tt -- ;
q[ ++ tt] = i or a[i];
}
KMP
char s[maxn],p[maxn];
int ne[maxn];
void get_next(char *s){
int m=strlen(s+1);
int i=1,j=0;
ne[1]=0;
while(i<=m){
if(j==0||s[i]==s[j])
ne[++i]=++j;
else
j=ne[j];
}
}
int kmp(char *s,char *p){
int i=1,j=1;
int n=strlen(s+1),m=strlen(p+1);
while(i<=n){
if(j==0||s[i]==p[j]){
++i,++j;
}
else
j=ne[j];
if(j==m+1){
return 1; //p为s的子串
}
}
return 0;
}
Trie
char s[maxn];
int tr[maxn][26],cnt[maxn],idx;
// maxn为n*len
// 0号点既是根节点,又是空节点
// tr[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// 插入一个字符串
void insert(char *s){
int p=0;
for(int i=0;s[i];i++){
int u=s[i]-'a';
if(!tr[p][u]) tr[p][u]=++idx;
p=tr[p][u];
}
cnt[p]++;
}
// 查询字符串出现的次数
int qurey(char *s){
int p=0;
for(int i=0;s[i];i++){
int u=s[i]-'a';
if(!tr[p][u]) return 0;
p=tr[p][u];
}
return cnt[p];
}
并查集
朴素并查集
//查询祖先编号
int fin(int x){
return re[x]==x?x:re[x]=fin(re[x]);
}
for(int i=1;i<=n;i++) re[i]=i;
//合并
int fx=fin(x),fy=fin(y);
re[fx]=fy;
种类并查集(食物链)
int re[maxn],d[maxn]; //d[x]存储x到re[x]的距离
int fin(int x){
if(re[x]!=x){
int fa=fin(re[x]); //找到祖先
d[x]+=d[re[x]]; //到祖先的距离等于x到父亲的距离加上父亲到祖先的距离
re[x]=fa; //路径压缩
}
return re[x]; //返回父亲节点
}
//初始化
for(int i=1;i<=n;i++) re[i]=i,d[i]=0;
//合并集合
if(fx!=fy){
re[fx]=fy;
d[fx]=distance; // 根据具体问题,初始化fx的偏移量
}
堆
小根堆
等同于priority_queue的堆
int heap[maxn],idx;
int n,m;
/* 小根堆
1.插入一个数 heap[++idx]=x;up(idx);
2.求集合当中最小值 heap[1];
3.删除最小值 swap(heap[1],heap[idx]);idx--;down(1);
4.删除任意一个元素 heap[k]=heap[idx];idx--;down(k);up(k);
5.修改任意一个元素 heap[k]=x;down(k);up(k);
6.创建堆 O(n) for(int i=n/2;i>=1;i--) down(i);
*/
void down(int u){
int v=u;
if(u*2<=idx&&heap[u*2]<heap[v]) v=u*2;
if(u*2+1<=idx&&heap[u*2+1]<heap[v])v=u*2+1;
if(u!=v){
swap(heap[u],heap[v]);
down(v);
}
}
void up(int u){
while(u/2&&heap[u/2]>heap[u]){
swap(heap[u/2],heap[u]);
u=u/2;
}
}
可以删除修改第k个插入的元素
维护2个数组,堆下标->数组下标 and 数组下标->堆下标
int heap[maxn],hp[maxn],ph[maxn],idx;
//heap为堆,hp为堆上编号映射到数组下标,ph为数组下标映射到堆上编号
void heap_swap(int u,int v){
swap(ph[hp[u]],ph[hp[v]]);
swap(hp[u],hp[v]);
swap(heap[u],heap[v]);
}
void up(int u){
while(u/2&&heap[u/2]>heap[u]){
heap_swap(u/2,u);
u=u/2;
}
}
void down(int u){
int t=u;
if(u*2<=idx&&heap[u*2]<heap[t]) t=u*2;
if(u*2+1<=idx&&heap[u*2+1]<heap[t]) t=u*2+1;
if(t!=u){
heap_swap(u,t);
down(t);
}
}
//插入
heap[++idx]=x;
ph[++pos]=idx,hp[idx]=pos;
up(idx);
//堆顶
cout<<heap[1]<<"\n";
//删除堆顶
heap_swap(1,idx);
idx--;
down(1);
//删除第k个插入的元素
k=ph[k];
heap_swap(idx,k);
idx--;
down(k);
up(k);
//修改第k个插入的元素
k=ph[k];
heap[k]=x;
down(k);
up(k);
哈希
一般哈希
/*开放寻址法*/
const int N = 200003, null = 0x3f3f3f3f;
//N为散列表大小,开2-3倍且为质数
//null为初始值,表明此位置没有被赋值过
int H[N];
int fin(int x){
int t = (x % N + N) % N;
while(H[t]!=null&&H[t]!=x){
t++;
if(t==N) t=0;
}
return t;
}
//初始化
memset(H,0x3f,sizeof H);
//插入
int t=fin(x);
H[t]=x;
//查找
cout<<(H[t]==x?"Yes\n":"No\n");
/*拉链法*/
const int N=100003;
//数据范围大小一倍且为质数
int H[N],e[N],ne[N],idx;
void insert(int x){
int t = (x % N + N) % N;
e[++idx]=x;ne[idx]=H[t];H[t]=idx;
}
int find(int x){
int t = (x % N + N) % N;
for(int i=H[t];i;i=ne[i]){
if(e[i]==x) return 1;
}
return 0;
}
//初始化
memset(H,0,sizeof H);
//插入
insert(x);
//查找
cout<<(find(x)?"Yes\n":"No\n");
字符串哈希
typedef unsigned long long ull;
const int maxn=1e5+5,P=131;
//随机模数P的经验值是131或13331,取这两个值的冲突概率低
ull H[maxn],base[maxn]; // h[k]存储字符串前k个字母的哈希值, base[k]存储 P^k mod 2^64
//预处理
base[0] = 1;
for (int i = 1; i <= n; i ++ ){
H[i] = H[i - 1] * P + s[i];
base[i] = base[i - 1] * P;
}
// 计算子串 s[l ~ r] 的哈希值
ull get_hash(int l,int r){
return H[r] - H[l - 1] * base[r - l + 1];
}
搜索与图论
DFS
递归,遍历所有情况,使用栈
BFS
解决最小步数问题,层次遍历,使用队列
树与图的存储
1.邻接矩阵
2.vector存图
3.邻接表
树
树的重心
int dfs(int u){
vi[u]=true;siz[u]=1;
int sum=0;
for(int i=h[u];i;i=ne[i]){
int v=e[i];
if(vi[v]) continue ;
int t=dfs(v);
sum=max(sum,t);
siz[u]+=t;
}
sum=max(sum,n-siz[u]);
ans=min(ans,sum);
return siz[u];
}
//ans为所求
树的直径
void dfs(int u, int fa){
for (int i = 0; i < edge[u].size(); i++){
int v = edge[u][i].first, val = edge[u][i].second;
if (v == fa)
continue;
dfs(v, u);
if (dp[v][0] + val > dp[u][0])
dp[u][1] = dp[u][0], dp[u][0] = dp[v][0] + val;
else if (dp[v][0] + val > dp[u][1])
dp[u][1] = dp[v][0] + val;
}
ans = max(dp[u][0] + dp[u][1], ans);
}
//ans为所求
图
拓扑排序
时间复杂度 O(n+m)
void tupo(){
queue<int>q;
for(int i=1;i<=n;i++){
if(!in[i]) q.push(i),ans.push_back(i);
}
while(q.size()){
int t=q.front();
q.pop();
for(int i=h[t];i;i=ne[i]){
int v=e[i];
if(--in[v]) continue;
q.push(v),ans.push_back(v);
}
}
if(ans.size()==n)// 存在拓扑排序,即不存在环
//拓扑排序不唯一,可用来判环
}
Dijkstra朴素版本
时间复杂是 O(n2+m)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5, inf = 0x3f3f3f3f, N = 505;
int n, m;
int g[N][N], dis[N];
bool vis[N];
int dijkstra(int st, int en){
memset(dis, 0x3f, sizeof dis);
dis[st] = 0;
for(int j=1;j<=n;j++){
int t=-1;
for(int i=1;i<=n;i++){
if(!vis[i]&&(t==-1||dis[i]<dis[t]))
t=i;
}
vis[t]=true;
for(int i=1;i<=n;i++){
if(dis[i]>dis[t]+g[t][i])
dis[i]=dis[t]+g[t][i];
}
}
if(dis[en]==inf) return -1;
return dis[en];
}
int main()
{
memset(g, 0x3f, sizeof g);
cin >> n >> m;
for (int i = 1; i <= n; i++)
g[i][i] = 0;
while (m--){
int x, y, z;
cin >> x >> y >> z;
g[x][y] = min(g[x][y], z);
}
cout << dijkstra(1, n) << "\n";
}
Dijkstra堆优化版本
时间复杂度 O(mlogn)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=1e5+5,inf=0x3f3f3f3f;
int n,m;
int e[N],w[N],ne[N],h[N],idx;
int dis[N];
bool vis[N];
void add(int x,int y,int z){
++idx,e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx;
}
int Dijkstra(int st,int en){
memset(dis,0x3f,sizeof dis);
priority_queue<pii,vector<pii>,greater<pii> >q;
dis[st]=0;
q.push({0,st});
while(q.size()){
auto t=q.top();
q.pop();
int u=t.second,distance=t.first;
if(vis[u]) continue;
vis[u]=true;
for(int i=h[u];i;i=ne[i]){
int v=e[i];
if(dis[v]>distance+w[i]){
dis[v]=distance+w[i];
q.push({dis[v],v});
}
}
}
if(dis[en]==inf) return -1;
return dis[en];
}
int main(){
cin>>n>>m;
while(m--){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
cout<<Dijkstra(1,n)<<"\n";
}
Bellman-Ford算法
时间复杂度 O(nm)
可求最多经过k条边的最短路
#include <bits/stdc++.h>
using namespace std;
const int N=505,M=1e5+5,inf=0x3f3f3f3f;
int n,m,k;
int dis[N],backup[N];
struct node{
int x,y,val;
}edge[M];
//O(n*m)
int bellman_ford(int st,int en){
memset(dis,0x3f,sizeof dis);
dis[st]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
auto t=edge[j];
dis[t.y]=min(dis[t.y],dis[t.x]+t.val);
}
}
if(dis[en]>inf/2) return -1;
return dis[en];
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
edge[i]={x,y,z};
}
int ans=bellman_ford(1,n);
cout<<ans<<"\n";
}
spfa算法
时间复杂度 平均情况下 O(m),最坏情况下 O(nm)。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e5+5,inf=0x3f3f3f3f;
int e[M],ne[M],w[M],h[N],idx;
int dis[N];
bool vis[N];
int n,m;
//O(k*n)
void add(int x,int y,int z){
++idx,e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx;
}
int spfa(int st,int en){
memset(dis,0x3f,sizeof dis);
dis[st]=0;
queue<int>q;
q.push(st);
vis[st]=true;
while(q.size()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=h[u];i;i=ne[i]){
int v=e[i],val=w[i];
if(dis[v]>dis[u]+val){
dis[v]=dis[u]+val;
if(!vis[v]){// 如果队列中已存在v,则不需要将v重复插入
q.push(v);
vis[v]=true;
}
}
}
}
if(dis[en]==inf) return -1;
return dis[en];
}
int main(){
cin>>n>>m;
while(m--){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
int ans=spfa(1,n);
if(ans==-1) puts("impossible");
else cout<<ans<<"\n";
}
spfa判断负环
时间复杂度是 O(nm)
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e5+5,inf=0x3f3f3f3f;
int e[M],ne[M],w[M],h[N],idx;
int cnt[N],dis[N];
bool vis[N];
int n,m;
//O(n*m)
void add(int x,int y,int z){
++idx,e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx;
}
bool SPFA(){
// 不需要初始化dist数组
// 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。
queue<int>q;
for(int i=1;i<=n;i++){
q.push(i);
vis[i]=true;
}
while(q.size()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=h[u];i;i=ne[i]){
int v=e[i],val=w[i];
if(dis[v]>dis[u]+val){
dis[v]=dis[u]+val;
cnt[v]=cnt[u]+1;
if(cnt[v]>=n) return true; //cnt[v]表示到v所经过n条边,即需要经过n+1个点,但只存在n个点松弛过程中必然出现一个点经过两次,即负环
if(!vis[v]){
q.push(v);
vis[v]=true;
}
}
}
}
return false;
}
int main(){
cin>>n>>m;
while(m--){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
if(SPFA()) cout<<"Yes\n";
else cout<<"No\n";
}
floyd算法
时间复杂度是 O(n^3)
#include<bits/stdc++.h>
using namespace std;
const int N=205,inf=0x3f3f3f3f;
int n,m,k;
int g[N][N];
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=i==j?0:inf;
while(m--){
int x,y,z;
cin>>x>>y>>z;
g[x][y]=min(g[x][y],z);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
while(k--){
int u,v;
cin>>u>>v;
if(g[u][v]>inf/2) puts("impossible");
else cout<<g[u][v]<<"\n";
}
}
最小生成树
prim算法
时间复杂度O(n^2)
#include<bits/stdc++.h>
using namespace std;
const int N=505,inf=0x3f3f3f3f;
int n,m;
int dis[N],g[N][N];
bool vis[N];
int prim(){
memset(dis,0x3f,sizeof dis);
int res=0;
for(int i=0;i<n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!vis[j]&&(t==-1||dis[j]<dis[t]))
t=j;
}
if(i&&dis[t]==inf) return -1;
vis[t]=true;
if(i) res+=dis[t];
for(int j=1;j<=n;j++){
dis[j]=min(dis[j],g[t][j]);
}
}
return res;
}
int main(){
memset(g,0x3f,sizeof g);
cin>>n>>m;
while(m--){
int x,y,z;
cin>>x>>y>>z;
g[x][y]=g[y][x]=min(g[x][y],z);
}
int ans=prim();
if(ans==-1) puts("impossible");
else cout<<ans<<"\n";
Kruskal算法
时间复杂度O(mlogm)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 2e5 + 5, inf = 0x3f3f3f3f;
int n, m;
int re[N];
struct node{
int x, y, val;
friend bool operator<(const node a, const node b){
return a.val < b.val;
}
} edge[M];
int fin(int x){
return re[x] == x ? re[x] : re[x] = fin(re[x]);
}
int kruscal()
{
int res = 0, cnt = 0;
sort(edge + 1, edge + m + 1);
for (int i = 1; i <= m; i++){
int fx = fin(edge[i].x), fy = fin(edge[i].y);
if (fx != fy){
re[fx] = fy;
res += edge[i].val;
cnt++;
}
}
if (cnt < n - 1) return -1;
return res;
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++) re[i] = i;
for (int i = 1; i <= m; i++){
int x, y, z;
cin >> x >> y >> z;
edge[i] = {x, y, z};
}
int ans = kruscal();
if (ans == -1) puts("impossible");
else cout << ans << "\n";
}
染色法判定二分图
时间复杂度O(n+m)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m;
int e[N],ne[N],h[N],idx;
int color[N];// 表示每个点的颜色,0表示为未染色,1表示黑色,2表示白色
void add(int x,int y){
e[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
// 奇偶染色
// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u,int c){
color[u]=c;
for(int i=h[u];i;i=ne[i]){
int v=e[i];
if(!color[v]){
if(!dfs(v,3-c)) return false;
}
else if(color[v]==c) return false;
}
return true;
}
int main(){
cin>>n>>m;
while(m--){
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++){
if(!color[i]){
if(!dfs(i,1)){
puts("No");
return 0;
}
}
}
puts("Yes");
}
匈牙利算法
二分图最大匹配
时间复杂度O(n*m)
#include<bits/stdc++.h>
using namespace std;
const int N=505,M=1e5+5;
int e[M],ne[M],h[N],idx;// 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int n1,n2,m;// n1表示第一个集合中的点数,n2表示第二个集合中的点数
int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool vis[N];// 表示第二个集合中的每个点是否已经被遍历过
void add(int x,int y){
e[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
int Hungary(int u){
for(int i=h[u];i;i=ne[i]) {
int v=e[i];
if(vis[v]) continue;
vis[v]=1;
if(!match[v]||Hungary(match[v])){
match[v]=u;
return true;
}
}
return false;
}
int main(){
cin>>n1>>n2>>m;
while(m--){
int x,y;
cin>>x>>y;
add(x,y);
}
int ans=0;
// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
for(int i=1;i<=n1;i++){
memset(vis,0,sizeof(vis));
ans+=Hungary(i);
}
cout<<ans<<"\n";
}
数学知识
质数
判断质数
bool check_prime(int x){
if(x<2) return false;
for(int i=2;i<=x/i;i++){
if(x%i==0) return false;
}
return true;
}
分解质因数
vector<pii>v;
void solve(int n){
v.clear();
for(int i=2;i<=n/i;i++){
if(n%i==0){
int cnt=0;
while(n%i==0) n/=i,cnt++;
v.push_back({i,cnt});
}
}
if(n>1) v.push_back({n,1});
}
素数筛
bool is_prime[maxn];
int prime[maxn],p_cnt;
void get_prime(int n){
is_prime[0]=is_prime[1]=-1;//非质数非合数
for(int i=2;i<=n;i++){
if(!is_prime[i]) prime[++p_cnt]=i;
for(int j=1;prime[j]<=n/i;j++){
is_prime[prime[j]*i]=true;
if(i%prime[j]==0) break;
}
}
}
约数
求所有约数
void get_yue(int n){
vector<int>v;
for(int i=1;i<=n/i;i++){
if(n%i==0){
v.push_back(i);
if(i!=n/i) v.push_back(n/i);
}
}
sort(v.begin(),v.end());
}
约数个数
如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和
如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)
最大公约数
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
欧拉函数
求欧拉函数
//phi(n)=n*(1-1/p1)*(1-1/p2)*...*(1-1/pm);
int get_phi(int n){
int res=n;
for(int i=2;i<=n/i;i++){
if(n%i==0){
res=res/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) res=res/n*(n-1);
return res;
}
筛法求欧拉函数
int prime[maxn],phi[maxn],p_cnt;
bool is_prime[maxn];
void get_phi(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!is_prime[i]) prime[++p_cnt]=i,phi[i]=i-1;
for(int j=1;prime[j]<=n/i;j++){
is_prime[i*prime[j]]=true;phi[i*prime[j]]=phi[i]*(prime[j]-1);
if(i%prime[j]==0) {
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
}
}
}
快速幂
int fast_pow(int a,int b,int p){
a%=p;
if(a==0) return 0;
int res=1;
while(b){
if(b&1) res=1ll*res*a%p;
a=1ll*a*a%p,b>>=1;
}
return res;
}
扩展欧几里得
// 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a,int b,int &x,int &y){
if(b==0){
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
//x=x+(b/d*K);
//y=y-(a/d*K);
}
高斯消元
double a[N][N];
int gauss(double a[][N]){
int c,r;
for(c=1,r=1;c<=n;c++) //c位列 r为行
{
int t=r;
for(int i=r;i<=n;i++){
if(fabs(a[i][c])>fabs(a[t][c]))
t=i;
}
if(fabs(a[t][c])<eps) continue;
for(int j=c;j<=n+1;j++) swap(a[t][j],a[r][j]);
for(int j=n+1;j>=c;j--) a[r][j]/=a[r][c];
for(int i=r+1;i<=n;i++)
if(fabs(a[i][c])>eps)
for(int j=n+1;j>=c;j--)
a[i][j]-=a[r][j]*a[i][c];
r++;
}
if(r<=n){
for(int i=r;i<=n;i++)
if(fabs(a[i][n+1])>eps)
return 2;//无解
return 1;//多解
}
for(int i=n;i>=1;i--){
for(int j=i+1;j<=n;j++)
a[i][n+1]-=a[i][j]*a[j][n+1];
}
return 0; //有唯一解
}
组合数
C(n,m)=C(n-1,m)+C(n-1,m-1)
n=1000
void C_init(int n){
for(int i=0;i<=n;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
}
预处理逆元
int fac[maxn],invfac[maxn];
inline int C(int n,int m){return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;}
void C_init(int N){
fac[0]=1;
for(int i=1;i<=N;i++){
fac[i] = 1ll*fac[i-1]*i%mod;
}
invfac[N] = fast_pow(fac[N],mod-2,mod);
for(int i=N-1;i>=0;i--) invfac[i] = 1ll*invfac[i+1]*(i+1)%mod; //1/(i-1)!=i/(i)!
}
Lucas
若p是质数,则对于任意整数 1 <= m <= n,有:
C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
mod=1e6
int C(int n,int m,int p){
if(m>n) return 0;
int up=1,down=1;
for(int i=1,j=n;i<=m;i++,j--){
up=1ll*up*j%p;
down=1ll*down*i%p;
}
return 1ll*up*fast_pow(down,p-2,p)%p;
}
int lucas(ll n,ll m,int p){
if(n<p&&m<p) return C(n,m,p);
return 1ll*C(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
}
高精度
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int prime[maxn],is_prime[maxn],mp[maxn];
int cnt_p;
void get_prime(int n){ // 线性筛法求素数
for(int i=2;i<=n;i++){
if(!is_prime[i]) prime[++cnt_p]=i;
for(int j=1;prime[j]<=n/i;j++){
is_prime[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
}
int get(int n,int p){// 求n!中p 的次数
int res=0;
while(n){
res+=n/p;
n/=p;
}
return res;
}
vector<int>mul(vector<int>&A,int b){ // 高精度乘低精度模板
vector<int>C;
int t=0;
for(int i=0;i<A.size();i++){
t=A[i]*b+t;
C.push_back(t%10);
t=t/10;
}
while(t) C.push_back(t%10),t/=10;
return C;
}
int main(){
get_prime(5000);
int n,m;
cin>>n>>m;
for(int i=1;prime[i]<=n;i++){
int p=prime[i];
mp[p]=get(n,p)-get(m,p)-get(n-m,p);
}
vector<int>res(1,1);
for(int i=1;prime[i]<=n;i++){
int p=prime[i];
for(int j=1;j<=mp[p];j++){
res=mul(res,p);
}
}
reverse(res.begin(),res.end());
for(auto it:res) cout<<it;
puts("");
return 0;
}
卡特兰数
给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为:
Cat(n) = C(2n, n) / (n + 1)
SG函数
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。