一些简单的入门算法模版
DFS
递归实现指数型枚举
void dfs(int u){
if(u>=n){
for(auto it:vec) cout<<it<<" ";
cout<<endl;
return ;
}
for(int i=1;i<=n;i++)
if(!st[i]){
st[i]=true; //选
vec.push_back(i);
dfs(u+1);
st[i]=false; //不选
vec.pop_back();
}
}
递归实现排列型枚举
//求1~n的全排列,共有2的n次方种情况
void dfs(int u){
if(u>n){ //已经选了n个数
for(auto it:vec) cout<<it<<" ";
cout<<endl;
return ;
}
for(int i=1;i<=n;i++) //排列与顺序无关,下标从1开始
if(!st[i]){ //选目前没选的数
st[i]=true;
vec.push_back(i);
dfs(u+1);
st[i]=false;
vec.pop_back();
}
}
BFS
迷宫问题
int n;
int g[N][N];
PII q[M];
PII pre[N][N];
int d[N][N];
void bfs(int sx,int sy){
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
memset(d,-1,sizeof d); //初始化
int hh=0,tt=0;
q[0]={sx,sy};
d[sx][sy]=0;
while(hh<=tt){
PII t=q[hh++];
for(int i=0;i<4;i++){
int a=t.x+dx[i],b=t.y+dy[i];
if(a>=0&&a<n&&b>=0&&b<n&&d[a][b]==-1&&g[a][b]==0){
d[a][b]=d[t.x][t.y]+1;
pre[a][b]=t; //记录上一个节点
q[++tt]={a,b};
}
}
}
}
//--------------------------------------------------------------
//若是求路径,可以bfs终点然后从始点遍历pre数组打印路径
PII end(0,0); //始点
while(true){
cout<<end.x<<" "<<end.y<<endl;
if(end.x==n-1&&end.y==n-1) break; //等打印到终点break
end=pre[end.x][end.y];
}
树的重心
//将重心删除后,剩余各个连通块中点数的最大值。
int ans=N; //记录答案
int n; //树的节点
bool st[N];
//我们需要记录删除重心后剩下联通块的最大值,剩下的联通块有当前节点的子节点和上面父节点组成的部分,所以比较的时候先比较子节点的数量,因此dfs子节点需要返回子节点的数量,然后总结点减去子节点数量+1为上面部分的数量比较求最大值,然后更新答案
int dfs(int u){ // 返回子节点的数量
st[u]=true;
int sum=1;
int res=0;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]){
int u=dfs(j); //子节点的数量
sum+=u;
res=max(res,u);
}
}
res=max(res,n-sum);
ans=min(ans,res);
return sum;
}
拓扑排序
int d[N]; //记录每个点的入度
void topsort(){
int q[N];
int hh=0,tt=-1;
for(int i=1;i<=n;i++) if(!d[i]) q[++tt]=i; //将入度为零的点加入队列
while(hh<=tt){
auto t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
d[j]--; //t所连的点入度减一
if(!d[j]) q[++tt]=j; //当点的入度为0时加入到队列里
}
}
if(tt==n-1){ //没有闭环最后队列会有n个数
for(int i=0;i<=tt;i++) cout<<q[i]<<" ";
}
else cout<<-1<<endl;
}
朴素版Dijkstra
const int N=1010;
int g[N][N];
int dist[N];
int n,m;
bool st[N];
void dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n;i++){ //共有n个点循环n次每次找到最近的点
int t=-1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||dist[j]<dist[t]))
t=j;
st[t]=true;
for(int j=1;j<=n;j++) //找到最近的点进行更新
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
if(dist[n]==0x3f3f3f3f) cout<<-1<<endl;
else cout<<dist[n]<<endl;
}
堆优化Dijkstra
int dist[N];
bool st[N];
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>>q;
q.push({0,1});
while(q.size()){
PII t=q.top();
q.pop();
int ver=t.second;
if(st[ver]) continue; //用改节点已经更新过最短路了continue
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[ver]+w[i]){
dist[j]=dist[ver]+w[i];
q.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
else return dist[n];
}
spfa
int dist[N];
bool st[N];
int spfa(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
st[1]=true;
queue<PII>q;
q.push({0,1});
while(q.size()){
PII t=q.front();
q.pop();
int ver=t.second;
st[ver]=false;
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[ver]+w[i]){
dist[j]=dist[ver]+w[i];
if(!st[j]){
st[j]=true;
q.push({dist[j],j});
}
}
}
}
return dist[n];
}
Floyd
int g[N][N];
void floyd(){
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]);
}
Kruskal求最小生成树
//并查集来求最小生成树
int n,m;
int p[N];
int res=0,cnt=0;
struct E{
int a,b,w; //a->b 边权为w
bool operator<(const E&rhs){
return this->w<rhs.w;
}
}e[M*2]; //节点
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void kruskal(){
for(int i=0;i<m;i++){ //遍历出所有的边
int a=e[i].a,b=e[i].b,w=e[i].w;
if(find(a)!=find(b)){ //a和b不在一个集合加入进去
res+=w;
p[find(a)]=find(b);
cnt++;
}
}
}
//---------------------
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) p[i]=i;
for(int i=0;i<m;i++){
int u,v,w;
cin>>u>>v>>w;
e[i]={u,v,w};
}
sort(e,e+m); //按边权w进行排序
kruskal();
if(cnt<n-1) cout<<"impossible"<<endl;
else cout<<res<<endl;
}
01背包
int v[N],w[N];
int f[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++) cin>>v[i]>>w[i];
for(int i=0;i<n;i++) //先物品在背包
for(int j=m;j>=v[i];j--) //01背包只有一个,保证每次更新不会被上次更新影响到背包从后遍历
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
}
完全背包c
int n,m;
int v[N],w[N];
int f[N];
int main(){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>v[i]>>w[i];
for(int i=0;i<n;i++)
for(int j=v[i];j<=m;j++) //防止数组越界背包从v[i]开始
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[m];
}
多重背包
int n,m;
int f[N];
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
int v,w,s; //重量,价值,个数
cin>>v>>w>>s;
for(int k=1;k<=s;k*=2){ //每一组进行01背包,每组按二进制枚举
for(int j=m;j>=k*v;j--)
f[j]=max(f[j],f[j-v*k]+w*k);
s-=k;
}
if(s){ //不够二进制数目的新算一组
for(int j=m;j>=s*v;j--)
f[j]=max(f[j],f[j-v*s]+w*s);
}
}
cout<<f[m]<<endl;
}
分组背包
int f[N],v[N][N],w[N][N],s[N]; //重量,价值,组数
int n,m;
int main(){
cin>>n>>m;
for(int i =0;i<n;i++){
cin>>s[i]; //第一组有几个
for(int j=0;j<s[i];j++)
cin>>v[i][j]>>w[i][j];
}
for(int i=0;i<n;i++) //物品只有一个01背包
for(int j=m;j>=0;j--)
for(int k=0;k<s[i];k++)
if(j>=v[i][k]) f[j]=max(f[j],f[j-v[i][k]]+w[i][k]); //每一组中选一个价值最大的
cout<<f[m]<<endl;
}
数字三角形
int a[N][N];
int n;
int dp[N][N];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
for(int i=0;i<=n;i++) //求最大,每个点初始化为最小值
for(int j=0;j<=i+1;j++)
dp[i][j]=-1e9;
dp[1][1]=a[1][1];
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++) //当前的点是由上面和斜上方转移过来的
dp[i][j]=a[i][j]+max(dp[i-1][j-1],dp[i-1][j]);
int res=-1e9;
for(int i=1;i<=n;i++) res=max(res,dp[n][i]); //枚举最后一行求最大值
cout<<res<<endl;
}
最长上升最序列 I
//状态表示:f[i]表示从第一个数字开始算,以a[i]的最大的上升序列。(以a[i]结尾的所有上升序列中属性为最大值的那一个)
const int N=1010;
int n;
int a[N],f[N];
int res=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
f[i]=1; //初始长度为1
for(int j=1;j<i;j++){ //遍历前面的点更新f[i]
if(a[j]<a[i]){
f[i]=max(f[i],f[j]+1);
}
}
res=max(res,f[i]);
}
cout<<res<<endl;
}
最长上升子序列 II
//单调栈优化可解决1e5的数据,贪心思路,始终保持目前维护的子序列中的数越小越好,当加入的数要比序列中最后一个小的时候直接加入队列里,否则用它替换队列中第一个>=的数
vector<int>p;
int main(){
int n,x;
cin>>n;
cin>>x;
p.push_back(x);
for(int i=1;i<n;i++){
cin>>x;
if(p.back()<x) p.push_back(x);
else *lower_bound(p.begin(),p.end(),x)=x;
}
cout<<p.size()<<endl;
}
最长公共子序列
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main(){
cin>>n>>m;
cin>>a+1>>b+1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
cout<<f[n][m];
}
区间合并
const int N=310;
int n;
int s[N];
int f[N][N];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++) s[i]+=s[i-1];
for(int len=2;len<=n;len++) //枚举长度
for(int i=1;i+len-1<=n;i++){
int l=i,r=i+len-1;
f[l][r]=1e9;
for(int k=l;k<=r;k++) //枚举分割点
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
cout<<f[1][n]<<endl;
}
整数划分
//现在给定一个正整数 n,请你求出n共有多少种不同的划分方法。
const int N=1010,mod=1e9+7;
int dp[N];
int main(){
int n;
cin>>n;
dp[0]=1; //初始化
for(int i=1;i<=n;i++) //可以看成是完全背包,用1~n的物品装满大小为n的背包
for(int j=i;j<=n;j++)
dp[j]=(dp[j]+dp[j-i])%mod; //总方案数为选第i件物品和不选第i件物品的方案总和
cout<<dp[n];
}
蒙德里安的梦想
//求把 N×M 的棋盘分割成若干个 1×2的长方形,有多少种方案。
//状态表示 f[i][j]: 前i-1列已经确定,且从第i-1列伸出的小方格在第i列的状态为j 的方案数。属性:个数。
const int N=12,M=1<<N;
typedef long long ll;
ll f[N][M];
bool st[M]; //存预处理所有情况
int n,m;
int main(){
while(cin>>n>>m,n||m){
memset(f,0,sizeof f);
for(int i=0;i<1<<n;i++){ //枚举一列中所有的状态,有2的n次方种
int cnt=0; //记录两个1之间的间隔
st[i]=true; //初始化为true
for(int j=0;j<n;j++){ //枚举出当前列的状态的每一位
if(i>>j&1){ //如果是1则需要判断两个1之间的位置是不是偶数
if(cnt&1){
st[i]=false;
break;
}
}
else cnt++; //不是1则cnt++
}
if(cnt&1) st[i]=false; //出来后需要在判断一次
}
f[0][0]=1; //初始化
for(int i=1;i<=m;i++) //枚举行
for(int j=0;j<1<<n;j++) //当前行的状态
for(int k=0;k<1<<n;k++) //上一行的状态
if((j&k)==0&&st[j|k]) //如果能从上一状态转移过来则更新答案
f[i][j]+=f[i-1][k];
cout<<f[m][0]<<endl;
}
}
最短Hamilton路径
//状态表示:f[i][j];集合:所有从0走到j,走过的所有点的情况是i的所有路径
//状态转移方程:f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j])
const int N=20,M=1<<N;
int n;
int w[N][N];
int f[M][N];
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>w[i][j];
memset(f,0x3f,sizeof f);
f[1][0]=0;
for(int i=0;i<1<<n;i++) //枚举所有状态
for(int j=0;j<n;j++) //第一个点
if(i>>j&1) //如果经过当前点的话
for(int k=0;k<n;k++) //第一个点的中转点
if(i-(1<<j)>>k&1) //若这个点存在则更新
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
cout<<f[(1<<n)-1][n-1]<<endl;
}
没有上司的舞会
void dfs(int u){
st[u]=true;
f[u][1]=happy[u]; //去
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]){
dfs(j);
f[u][1]+=f[j][0]; //去的话孩子不去
f[u][0]+=max(f[j][1],f[j][0]); //不去的话孩子可去可不去
}
}
}
滑雪
int dp(int x,int y){
int &v=f[x][y];
if(v!=-1) return v; //若是求出来过则直接返回
v=1;
for(int i=0;i<4;i++){
int xx=x+dx[i],yy=y+dy[i];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&h[x][y]>h[xx][yy]){
v=max(v,dp(xx,yy));
}
}
return v;//加上当前点能滑的最远的距离
}
一个简单的整数问题I
//给定一个正整数数列 a1,a2,…,an,每一个数都在 0∼p−1之间。
//可以对这列数进行两种操作:
//添加操作:向序列后添加一个数,序列长度变成 n+1;
//询问操作:询问这个序列中最后 L个数中最大的数是多少。
//程序运行的最开始,整数序列为空。一共要对整数序列进行 m次操作。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
const int N=2e5+10;
int m,p;
struct node{
int l,r;
int v;
}tr[N*4];
void pushup(int u){
tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
}
void build(int u,int l,int r){
tr[u]={l,r};
if(l==r) return ;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
}
int query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].v;
int mid=tr[u].l+tr[u].r>>1;
int v=0;
if(l<=mid) v=query(u<<1,l,r);
if(r>mid) v=max(v,query(u<<1|1,l,r));
return v;
}
void modify(int u,int x,int v){
if(tr[u].l==x&&tr[u].r==x) tr[u].v=v;
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
int main(){
int n=0,last=0;
cin>>m>>p;
build(1,1,m);
string op;
while(m--){
cin>>op;
if(op[0]=='A'){
int x;
cin>>x;
modify(1,n+1,((ll)last+x)%p);
n++;
}else{
int L;
cin>>L;
last=query(1,n-L+1,n);
cout<<last<<endl;
}
}
}
一个简单的整数问题II
//给定一个长度为 N的数列 A,以及 M条指令,每条指令可能是以下两种之一:
//C l r d,表示把 A[l],A[l+1],…,A[r]都加上 d
//Q l r,表示询问数列中第 l∼r个数的和。
//对于每个询问,输出一个整数表示答案。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int N = 1e5 + 10;
int n, m;
int w[N];
struct Node
{
int l, r;
LL sum, add;
}tr[N * 4];
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.add)
{
//传递懒标记,更新子树
left.add += root.add, left.sum += (LL) (left.r - left.l + 1) * root.add;
right.add += root.add, right.sum += (LL) (right.r - right.l + 1) * root.add;
//删除父结点懒标记
root.add = 0;
}
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = {l, r, w[l], 0};
else
{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int v)
{
if (l <= tr[u].l && tr[u].r <= r)
{
tr[u].sum += (tr[u].r - tr[u].l + 1) * v;
tr[u].add += v;
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, v);
if (r > mid) modify(u << 1 | 1, l, r, v);
pushup(u);
}
}
LL query(int u, int l, int r)
{
if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL v = 0;
if (l <= mid) v = query(u << 1, l, r);
if (r > mid) v += query(u << 1 | 1, l, r);
return v;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &w[i]);
build(1, 1, n);
char op[2];
int l, r, t;
while (m -- )
{
scanf("%s%d%d", op, &l, &r);
if (*op == 'Q') printf("%lld\n", query(1, l, r));
else
{
scanf("%d", &t);
modify(1, l, r, t);
}
}
return 0;
}