【算法】一些有用的知识
前言
本篇文章收录那些一般不会考裸题,但是常用于算法优化等处的算法们。
预计会有以下几种板块:
- 数学
- 字符串
- 其他
一、数学
1. 快速幂
int qpow(int x,int y){
int cur=1;
while(y){
if(y&1) cur=1ll*cur*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return cur;
}
2. 矩阵快速幂※
struct mat{
int a[N][N];
}a;
mat matmul(mat x,mat y){
mat cur;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cur.a[i][j]=0;
for(int k=0;k<n;k++){
for(int i=0;i<n;i++){
if(x.a[i][k]==0) continue;
for(int j=0;j<n;j++){
if(y.a[k][j]==0) continue;
cur.a[i][j]=(cur.a[i][j]+x.a[i][k]*y.a[k][j])%mod;
}
}
}
return cur;
}
void matpow(int x){
mat cur;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
cur.a[i][j]=0;
cur.a[i][i]=1;
}
/*
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
cout<<a.a[i][j]<<" ";
cout<<"\n";
}
*/
// int cnt=0;
while(x){
if(x&1) cur=matmul(cur,a);
a=matmul(a,a);
// cnt++;
x>>=1;
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
cout<<cur.a[i][j]<<" ";
cout<<"\n";
}
}
3. 单值求逆元
费马小定理:(适用于模数为质数)
//求 n 在 mod p 意义下的逆元
int qpow(int x,int y,int p){}//见快速幂模板
int main(){
cin>>n>>p;
cout<<qpow(n,p-2,p);
return 0;
}
欧拉定理:(适用于两个数互质)※
//求 n 在 mod p 意义下的逆元
int qpow(int x,int y,int p){}//见快速幂模板
void varphi_all(){//求 1~n 的所有数的欧拉函数
vis[1]=true;
for(int i=2;i<=n;i++){
if(vis[i]==false){
phi[i]=i-1;
prime.push_back(i);
}
for(int j=0;j<prime.size();j++){
int v=prime[j];
if(i*v>n) break;
vis[i*v]=true;
phi[i*v]=phi[i]*phi[v];
if(i%v==0){
phi[i*v]=phi[i]*v;
break;
}
}
}
}
int varphi_one(int x){//只求 x 的欧拉函数
int cur=n;
for(int i=2;i*i<=n;i++){
if(x%i==0){
cur-=cur/i;
while(x%i==0) x/=i;
}
}
if(x>1) cur-=cur/x;
return cur;
}
int main(){
cin>>n>>p;
cout<<qpow(n,varphi_one(n)-1,p);
return 0;
}
扩展欧几里得算法※
void exgcd(int &x,int &y,int a,int b){
if(b==0){
x=1,y=0;
return;
}
exgcd(x,y,b,a%b);
int tmp=x;
x=y;
y=tmp-a/b*y;
}
int main(){
ios::sync_with_stdio(false);
cin>>a>>b;
exgcd(x,y,a,b);
while(x<=0) x+=b;
cout<<x;
return 0;
}
4. 扩展欧拉定理※
//还要开个 long long
inline int varphi(int x){}//见求单个的欧拉函数模板
inline int qpow(int x,int y,int p){}//见快速幂模板
signed main(){
ios::sync_with_stdio(false);
string s;
cin>>a>>n>>s;
int m=varphi(n);
bool f=false;
for(int i=0;i<s.size();i++){
int x=s[i]-'0';
b=b*10+x;
if(b>=m){
b=b%m;
f=true;
}
}
if(f==true) b+=m;
cout<<qpow(a,b,n);
return 0;
}
5. 逆元的性质
线性求逆元※
inv[1]=1;
for(int i=1;i<=n;i++) inv[i]=(p-p/i)*inv[p%i]%p;
阶乘逆元※
inv[n+1]=qpow(ti[n],p-2,p);
for(int i=n;i>=1;i--) inv[i]=1ll*inv[i+1]*a[i]%p;
6. 预处理组合数※
for(int i=0;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
二、字符串
1. Trie※
int n,m;
int nex[N][30],cnt;
//nex表示下一个孩子的编号
//cnt是已经存储的编号
bool vis[N],las[N];
//vis是针对与这道题目的,表示以前是否访问过
//las表示该节点是不是单词的末尾
void update(string &s){//&代表只读取这个字符串,但不更改
int now=0;
for(int i=0;i<s.size();i++){
int tmp=s[i]-'a';
if(nex[now][tmp]==0) nex[now][tmp]=++cnt;//如果以前没访问过就要增点
now=nex[now][tmp];//访问它的儿子
}
las[now]=true;//这个节点就是单词末尾
}
int query(string &s){
//return 0:存在这个单词,并且以前没有访问过
//return 1:不存在这个单词
//return 2:存在这个单词,但以前访问过
int now=0;
for(int i=0;i<s.size();i++){
int tmp=s[i]-'a';
if(nex[now][tmp]==0) return 1;//还没有这个节点,表示根本就没这个单词
now=nex[now][tmp];
}
if(las[now]==false) return 1;//不是单词,但是已出现的单词的一个前缀
else if(vis[now]==false){
vis[now]=true;
return 0;
}
return 2;
}
2. KMP※
void kmp(string &s){
int x=0;
for(int i=1;i<s.size();i++){
while(x!=0&&s[x]!=s[i]) x=fail[x-1];
if(s[x]==s[i]) x++;
fail[i]=x;
}
}
void fin(string &s,string &t){
int x=0;
for(int i=0;i<s.size();i++){
while(x!=0&&t[x]!=s[i]) x=fail[x-1];
if(t[x]==s[i]) x++;
if(x==t.size()){
cout<<i-x+2<<"\n";
x=fail[x-1];
}
}
}
3. Manacher※
void manacher(){
int mid=0,r=0;
for(int i=1;i<n;i++){
if(i<=r)
d[i]=min(d[mid*2-i],r-i+1);
else d[i]=1;
while(s[i+d[i]]==s[i-d[i]]) d[i]++;
if(i+d[i]-1>r) r=i+d[i]-1,mid=i;
}
}
三、其他
1. Johnson 全源最短路※
bool spfa(int s){
queue<int>q;
memset(h,0x3f,sizeof(h));
h[s]=0;
cnt[s]=1;
vis[s]=true;
q.push(s);
while(q.empty()==false){
int x=q.front();
q.pop();
vis[x]=false;
if(cnt[x]>n) return false;
for(int i=head[x];i;i=edge[i].nex){
int v=edge[i].to;
if(h[v]<=h[x]+edge[i].w) continue;
h[v]=h[x]+edge[i].w;
if(vis[v]==true) continue;
vis[v]=true;
q.push(v);
cnt[v]++;
}
}
return true;
}
void dijkstra(int s){
for(int i=1;i<=n;i++) dis[i]=(int)1e9;
memset(vis,0,sizeof(vis));
priority_queue<Node>q;
dis[s]=0;
Node sta={s,0};
q.push(sta);
while(q.empty()==false){
int x=q.top().id;
q.pop();
if(vis[x]==true) continue;
vis[x]=true;
for(int i=head[x];i;i=edge[i].nex){
int v=edge[i].to;
if(dis[v]<=dis[x]+edge[i].w) continue;
dis[v]=dis[x]+edge[i].w;
Node nex={v,dis[v]};
q.push(nex);
}
}
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
for(int i=1;i<=n;i++) add(0,i,0);
if(spfa(0)==false){
cout<<"-1\n";
return 0;
}
for(int i=1;i<=m;i++){
int x=edge[i].fro,y=edge[i].to;
edge[i].w+=h[x]-h[y];
}
for(int i=1;i<=n;i++){
dijkstra(i);
long long sum=0;
for(int j=1;j<=n;j++){
if(dis[j]==(int)1e9){
sum+=(long long)j*(int)1e9;
}
else sum+=(long long)j*(long long)(dis[j]+h[j]-h[i]);
}
cout<<sum<<"\n";
}
return 0;
}
2. 差分约束※
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5;
int n,m;
int head[N<<1],tot;
int dis[N],cnt[N];
bool f,vis[N];
struct node{
int nex,to,w;
}edge[N<<1];
void add(int x,int y,int w){
edge[++tot].to=y;
edge[tot].nex=head[x];
edge[tot].w=w;
head[x]=tot;
}
void spfa(int s){
queue<int>q;
for(int i=1;i<=n;i++) dis[i]=INT_MAX;
dis[s]=0;
vis[s]=true;
cnt[s]=1;
q.push(s);
while(q.empty()==false){
int x=q.front();
q.pop();
vis[x]=false;
if(cnt[x]>n) return;
for(int i=head[x];i;i=edge[i].nex){
int v=edge[i].to,w=edge[i].w;
if(dis[v]<=dis[x]+w) continue;
dis[v]=dis[x]+w;
if(vis[v]==false){
vis[v]=true;
q.push(v);
cnt[v]++;
}
}
}
f=true;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
add(y,x,z);
}
for(int i=1;i<=n;i++) add(0,i,0);
spfa(0);
if(f==false) cout<<"NO";
else for(int i=1;i<=n;i++) cout<<dis[i]<<" ";
return 0;
}
3. 离散化
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y;
lisan[++m]=a[i].x,lisan[++m]=a[i].y;
}
sort(lisan+1,lisan+m+1);
m=unique(lisan+1,lisan+m+1)-lisan-1;
for(int i=1;i<=n;i++){
a[i].x=lower_bound(lisan+1,lisan+m+1,a[i].x)-lisan;
a[i].y=lower_bound(lisan+1,lisan+m+1,a[i].y)-lisan;
}