提高组所有知识模板大合集
看到哪个写哪个
排序
1.sort
2.归并排序
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int d[2000005],g[2000005],top;
void merge(int l,int r,int *c){
int mid=l+r>>1;
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r){
if(d[i]<d[j]) c[k++]=d[i++];
else c[k++]=d[j++];
}
while(i<=mid) c[k++]=d[i++];
while(j<=r) c[k++]=d[j++];
}
void solve(int l,int r){
if(l==r) return ;
if(r-l==1){
if(d[l]>d[r]) swap(d[l],d[r]);
return ;
}
int mid=l+r>>1;
solve(l,mid);
solve(mid+1,r);
merge(l,r,g);
for(int i=l;i<=r;i++) d[i]=g[i];
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>d[i];
solve(1,n);
for(int i=1;i<=n;i++) cout<<g[i]<<' ';
return 0;
}
3.堆排序
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,len,heap[N];
void push(int x){
if(x==1||heap[x>>1]<=heap[x]) return;
swap(heap[x>>1],heap[x]),push(x>>1);
}
void pop(int x){
int y=x;
if((x<<1)<=len&&heap[x<<1]<heap[x]) y=x<<1;
if((x<<1|1)<=len&&heap[x<<1|1]<heap[y]) y=x<<1|1;
if(y!=x) swap(heap[x],heap[y]),pop(y);
}
void solve(){
cin>>n;
while(len<n) cin>>heap[++len],push(len);
while(len) cout<<heap[1]<<' ',heap[1]=heap[len--],pop(1);
}
signed main(){
solve();
return 0;
}
暂时只记录这三种
最短路
1.Dijkstra
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int he[N],net[N<<1],vre[N<<1],w[N<<1];
int vis[N],dis[N];
int n,m,tot;
struct node {
int ds,p;
};
bool operator <(node a,node b) {
return a.ds>b.ds;
}
inline void add(int x,int y,int z) {//邻接表记录
++tot;
w[tot]=z;
vre[tot]=y;
net[tot]=he[x];
he[x]=tot;
}
priority_queue<node> q;
inline void Dijkstra(int x) {
memset(dis,0x3f,sizeof dis);
dis[x]=0;
q.push({0,x});
while(q.size()) {
node tmp=q.top();
q.pop();
int y=tmp.p;
vis[y]=1;
for(int i=he[y]; i; i=net[i]) {
int u=vre[i];
if(!vis[u]&&dis[u]>dis[y]+w[i]) {
dis[u]=dis[y]+w[i];
q.push({dis[u],u});
}
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1; i<=m; ++i) {
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
Dijkstra(1);
if(dis[n]==0x3f3f3f3f) {//没被更新
cout<<-1;
return 0;
}
cout<<dis[n];
return 0;
}
2.SPFA
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int he[N],net[N<<1],vre[N<<1],w[N<<1];
int vis[N],dis[N];
int n,m,tot;
inline void add(int x,int y,int z){
++tot;
w[tot]=z;
vre[tot]=y;
net[tot]=he[x];
he[x]=tot;
}
queue<int> q;
inline void SPFA(int x){
memset(dis,0x3f,sizeof dis);
dis[x]=0;
vis[x]=1;
q.push(1);
while(!q.empty()){
int y=q.front();
q.pop();
vis[y]=0;
for(int i=he[y];i;i=net[i]){
int u=vre[i];
if(dis[u]>dis[y]+w[i]){
dis[u]=dis[y]+w[i];
if(vis[u]==0) {
vis[u]=1;
q.push(u);
}
}
}
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
SPFA(1);
if(dis[n]>=0x3f3f3f3f){
cout<<-1;
}
else cout<<dis[n];
return 0;
}
3.Floyd
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
字符串
好像只会KMP
1.KMP
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
string u,v;
int ne[N],f[N];
int main(){
cin>>u>>v;
int n=u.size(),m=v.size();
u=' '+u,v=' '+v;
ne[1]=0;
for(int i=2,j=0;i<=m;i++){
while(j && v[i]!=v[j+1])j=ne[j];
if(v[i]==v[j+1])j++;
ne[i]=j;
}
for(int i=1,j=0;i<=n;i++){
while(j && u[i]!=v[j+1])j=ne[j];
if(u[i]==v[j+1])j++;
if(j==m) cout<<i-j+1<<'\n';
}
for(int i=1;i<=m;i++)cout<<ne[i]<<' ';
return 0;
}
hash
Hash 的核心思想在于,将输入映射到一个值域较小、可以方便比较的范围。
对于字符串hash
在 Hash 函数值不一样的时候,两个字符串一定不一样;
在 Hash 函数值一样的时候,两个字符串不一定一样(但有大概率一样,且我们当然希望它们总是一样的)。
我们将 Hash 函数值一样但原字符串不一样的现象称为哈希碰撞。
背包/dp
1.01背包
for(int i=1;i<=n;i++)
for(int j=v;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
2.完全背包
for(int i=1;i<=n;i++){
for(int j=1;j<=vo;j++){
if(j>=v[i])
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}
3.多重背包
for(int i=1;i<=n;i++)
for(int j=v;j>=w[i];j--)
for(int k=1;k*w[i]<=j&&k<=num[i];k++)
dp[j]=max(dp[j],dp[j-w[i]*k]+val[i]*k);
注意:完全背包,01背包和多重背包的不同在于选择次数的不同,同时也决定了遍历的方法
4.常见的dp模板
1.最长上升子序列 LIS
for(int i=1;i<=n;i++) dp[i]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i-1;j++)
if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);
for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
2.最长公共子序列 LCS
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
dp[i&1][j]=max(dp[(i-1)&1][j,dp[i&1][j-1]]);
if(a[i]==b[j]) dp[i&1][j]=max(dp[i&1][j],dp[(i-1)&1][j-1]);
}
搜索
没有模板,只有一些剪枝方法
1.常用剪枝
- 可行性剪枝
- 最优性剪枝 (对于当前答案已经大于之前答案的,直接退出)
- 记忆化 (对于已经搜过的节点进行记录)
2.迭代加深
用类似于广搜的方法去做深搜,以免深度过大但答案又在浅层带来的巨量时间复杂度。
每次设定一个深度,一但到达深度仍没有找到答案就停止。
3.A*和IDA*
A*是导航,IDA*是预警。
::::info[A*就是BFS+估价函数]
每一次BFS算出当前层的估价函数,遍历估价函数最小的。
::::
::::info[IDA*就是DFS+迭代加深+估价函数]
以迭代加深DFS的框架,将深度限制设置为: 若当前深度+未来估计步数>深度限制,则直接退出
::::
基础算法
1.快速幂和龟速乘
int ksm(int x,int k){//快速幂
int ans=1;
while(k){
if(k&1) ans=ans*x%p;
x=x*x%p;
k>>=1;
}
return ans;
}
int mul(int a,int b,int mod){//龟速乘
int ans=0;
while(b){
if(b&1) ans=(ans+a)%mod;
a=(a+a)%mod,b>>=1;
}
return ans;
}
2.差分和前缀和
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=a[i]+sum[i-1];//前缀和
c[i]=a[i]-a[i-1];//差分
}
3.双指针
同时使用两个指针,在序列、链表结构上指向的是位置,在树、图结构中指向的是节点,通过或同向移动,或相向移动来维护、统计信息。
e.g. 维护区间信息(常用),子序列匹配
4.位运算
与,或,异或
| 运算 | 运算符 | 数学表达 | 解释 |
|---|---|---|---|
| 与 | \(\&\) | \(\&\),\(and\) | 只有两个对应位都为 1 时才为 1 |
| 或 | | | |,\(or\) | 只要两个对应位中有一个 1 时就为 1 |
| 异或 | \(^\) | \(\oplus,xor\) | 只有两个对应位不同时才为 1 |
取反
取反暂无默认的数学符号表示,其对应的运算符为 ~。它的作用是把 𝑛𝑢𝑚 的二进制补码中的0 和 1 全部取反(0 变为 1,1 变为 0)。有符号整数的符号位在 ~ 运算中同样会取反。
左移和右移
\(num\) << \(i\) 表示将\(num\)的二进制表示向左移动\(i\)位所得的值。
\(num\) >> \(i\) 表示将\(num\)的二进制表示向右移动\(i\)位所得的值。
通常用\((1<<n)\)替换\(2^n\)
位运算通常用于状压DP,或对于某种状态的存储
基础数据结构
栈,堆,队列
1.栈
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int T,n,m;
int d[2000005],top;
signed main(){
cin>>T;
while(T--){
cin>>n;
top=0;
for(int i=1;i<=n;i++){
string s;cin>>s;
if(s=="push"){
int x;cin>>x;
d[++top]=x;
}else if(s=="query"){
if(top==0)cout<<"Anguei!\n";
else cout<<d[top]<<'\n';
}else if(s=="pop"){
if(top==0) cout<<"Empty\n";
else top--;
}else {
cout<<top<<'\n';
}
}
}
return 0;
}
2.堆排序写了
3.队列
#include<bits/stdc++.h>
using namespace std;
int n, m, i, j, k;
queue<int> q;
int main(){
cin>>n;
for(i=1;i<=n;i++){
int op,x;
cin>>op;
if(op==1) cin>>x,q.push(x);
else if (op == 2){
if (q.empty()) cout<<"ERR_CANNOT_POP\n";
else q.pop();
}
else if(op==3){
if(q.empty()) cout<<"ERR_CANNOT_QUERY\n";
else cout<<q.front()<<'\n';
}
else cout<<q.size()<<'\n';
}
return 0;
}
数学
dp优化
倍增与数据结构
单调队列与斜率优化
四边形不等式与决策单调性
计数dp、数位dp(记忆化搜索)
概率期望dp
图论
LCA
int lca(int a,int b){
if(dep[a]<dep[b]) swap(a,b);
for(int i=22;i>=0;i--)
if(dep[f[a][i]]>=dep[b]) a=f[a][i];
if(a==b) return a;
for(int i=22;i>=0;i--)
if(f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i];
return f[a][0];
}
树链剖分
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int s=0,f=1;char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*f;
}
const int N=5e5+10;
int n,m,rt,cnt,idx,a[N],M,sz[N],dep[N],f[N],son[N],top[N];
vector<int> v[N];
int id[N];//存储链上的新序号
int w[N];//存贮以链为序号下标的权值
int sum[N],tag[N];
int res;
void build(int p,int l,int r){
if(l==r){
sum[p]=w[l]%M;
return ;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
sum[p]=(sum[p<<1]+sum[p<<1|1])%M;
}
void pushdown(int p,int l,int r){
if(tag[p]){
int mid=l+r>>1;
int left=p<<1;
int right=p<<1|1;
tag[left]=(tag[left]+tag[p])%M;
tag[right]=(tag[right]+tag[p])%M;
sum[left]=(sum[left]+(mid-l+1)*tag[p]%M)%M;
sum[right]=(sum[right]+(r-mid)*tag[p]%M)%M;
tag[p]=0;
}
}
void change(int p,int l,int r,int x,int y,int k){
if(x<=l && r<=y){
tag[p]=(tag[p]+k)%M;
sum[p]=(sum[p]+(r-l+1)*k)%M;
return ;
}
pushdown(p,l,r);
int mid=l+r>>1;
if(x<=mid) change(p<<1,l,mid,x,y,k);
if(y>mid) change(p<<1|1,mid+1,r,x,y,k);
sum[p]=(sum[p<<1]+sum[p<<1|1])%M;
}
int ask(int p,int l,int r,int x,int y){
if(x<=l && r<=y){
return sum[p]%M;
}
pushdown(p,l,r);
int mid=l+r>>1,res=0;
if(x<=mid) res=(res+ask(p<<1,l,mid,x,y))%M;
if(y>mid) res=(res+ask(p<<1|1,mid+1,r,x,y))%M;
return res%M;
}
void chan(int p,int l,int r,int x,int y){
if(x<=l && r<=y){
res=(res+sum[p])%M;
return ;
}
pushdown(p,l,r);
int mid=l+r>>1;
if(x<=mid) chan(p<<1,l,mid,x,y);
if(y>mid) chan(p<<1|1,mid+1,r,x,y);
}
void update(int x,int y,int k){
k%=M;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
change(1,1,n,id[top[x]],id[x],k);
x=f[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
change(1,1,n,id[x],id[y],k);
}
int query(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res=0;
chan(1,1,n,id[top[x]],id[x]);
ans=(ans+res)%M;
x=f[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
res=0;
chan(1,1,n,id[x],id[y]);
ans=(ans+res)%M;
return ans;
}
void dfs1(int x,int fa){
f[x]=fa;
sz[x]=1;
dep[x]=dep[fa]+1;
int maxs=-1;
for(int i=0;i<v[x].size();i++){
int y=v[x][i];
if(y==fa) continue;
dfs1(y,x);
sz[x]+=sz[y];
if(sz[y]>=maxs) son[x]=y,maxs=sz[y];//处理重儿子
}
}
void dfs2(int x,int topp){//topp表示当前链最顶端的
id[x]=++cnt;
w[cnt]=a[x];
top[x]=topp;
if(!son[x]) return ;
dfs2(son[x],topp);//先跑重儿子
for(int i=0;i<v[x].size();i++){
int y=v[x][i];
if(y==f[x] || y==son[x]) continue;
dfs2(y,y);
}
}
signed main() {
n=read(),m=read(),rt=read(),M=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
v[x].push_back(y);
v[y].push_back(x);
}
dfs1(rt,0);
dfs2(rt,rt);
build(1,1,n);
int opt,x,y,z;
while(m--){
opt=read();
if(opt==1){
x=read(),y=read(),z=read();
// 将树从 x 到 y 结点最短路径上所有节点的值都加上 z。
update(x,y,z);
}
else if(opt==2){
x=read(),y=read();
// 查询求树从 x 到 y 结点最短路径上所有节点的值之和。
cout<<query(x,y)<<'\n';
}
else if(opt==3){
x=read(),z=read();
// 将以x为根的子树所有节点值加上z
change(1,1,n,id[x],id[x]+sz[x]-1,z);
}
else if(opt==4){
x=read();
// 查询以x为根的子树所有节点值之和
cout<<ask(1,1,n,id[x],id[x]+sz[x]-1)<<'\n';
}
}
return 0;
}
tarjan与强连通分量
tarjan 模板
void tarjan(int x){
low[x]=dfn[x]=++idx;
ins[x]=1;
st[++top]=x;
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
int y;cnt++;
do{
y=st[top--];
scc[y]=cnt;
ins[y]=0;
sz[cnt]++;
}while(y!=x);
if(sz[cnt]>1) ans++;
}
}
缩点
加上这几句就可以了
for(int i=0;i<v.size();i++){
int x=v[i].x,y=v[i].y;
if(scc[x]!=scc[y])
add(scc[x],scc[y]),du[scc[y]]++;
}
割点(割顶)
void tarjan(int x){
low[x]=dfn[x]=++idx;
int son=0;
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(!dfn[y]){
son++;
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]==dfn[x])
if(x!=root || son>1) c[x]=1;//记录他是割点
}else low[x]=min(low[x],dfn[y]);
}
}
边双/点双连通分量
边双连通分量
//注意: add的tot要从1开始
void tarjan(int x,int com){
low[x]=dfn[x]=++idx;
st[++top]=x;
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(!dfn[y]){
tarjan(y,i);
low[x]=min(low[x],low[y]);
}
else if((i^1)!=com)//防止是上一条边
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
int y;cnt++;
do{
y=st[top--];
edcc[y]=cnt;
}while(y!=x);
}
}
点双连通分量
//注意: add的tot要从1开始
void tarjan(int x){
low[x]=dfn[x]=++idx;
st[++top]=x;
if(!head[x]){
v[++cnt].push_back(x);
}
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]){//直接在这里更新(也可以拿到外面)
cnt++;
int z;
while(true){
z=st[top--];
vdcc[z]=cnt;
v[cnt].push_back(z);
if(z==y) break;
}
v[cnt].push_back(x);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
2-SAT适定性问题
模板
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*f;
}
const int N=2e6+5;
int n,m;
int ver[N],head[N],ne[N],tot=0;
void add(int x,int y){
ver[++tot]=y,ne[tot]=head[x],head[x]=tot;
}
int dfn[N],low[N],scc[N],st[N],top,idx,cnt;
void tarjan(int x){
dfn[x]=low[x]=++idx;
st[++top]=x;
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}else if(!scc[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
int y;++cnt;
do{
y=st[top--];
scc[y]=cnt;
}while(y!=x);
}
}
int main(){
n=read(),m=read();
for(int a=1;a<=m;a++){
int i=read(),x=read(),j=read(),y=read();
add(i+(!x)*n,j+y*n);
add(j+(!y)*n,i+x*n);
}
for(int i=1;i<=2*n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++)
if(scc[i]==scc[i+n]){
cout<<"IMPOSSIBLE";
return 0;
}
cout<<"POSSIBLE\n";
for(int i=1;i<=n;++i){
if(scc[i+n]<scc[i]) printf("1 ");
else printf("0 ");
}
return 0;
}
二分图
判定二分图
染色法:
bool dfs(int x){
vis[x]=1;
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(vis[y]){
if(c[y]==c[x]) return 0;
}
else {
c[y]=c[x]^1;
if(!dfs(y)) return 0;
}
}
return 1;
}
二分图最大匹配(匈牙利算法)
bool find(int x){
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(vis[y]) continue;
vis[y]=1;
if(!f[y] || find(f[y])){
f[y]=x;
return 1;
}
}
return false;
}
void Hungary(){
for(int i=1;i<=n;i++){//枚举左端点
memset(vis,0,sizeof vis);
if(find(i)) ans++;//如果找到了一条增广路
}
}
基环树
一个有且只有一个环的图
本文来自博客园,作者:{昕木},转载请注明原文链接:https://www.cnblogs.com/Austin0928/articles/19179179

浙公网安备 33010602011771号