动态规划
状态压缩DP
蒙德里安的梦想
/*
核心:先放横着的,再放竖着的
f[i,j]表示已经将前i-1列摆好,且从第i-1列伸出到第i列的状态是j的所有方案
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=12, M=1<<N;
int n, m;
ll f[N][M];
ll st[M]; //记录状态是否合法
vector<int> state[M]; //存储合法的状态
int main()
{
while(cin>>n>>m, n||m)
{
//预处理1
for(int i=0; i< 1<<n; i++)
{
st[i]=1; //先假设状态合法 不可以放在最后加else 可以写成函数
int cnt=0;
for(int j=0; j<n; j++)
{
if(i>>j&1) //第j位为1 判断上面连续的0是否是偶数个 合法状态
{
if(cnt&1) st[i]=0;
cnt=0;
}
else cnt++;
}
if(cnt&1) st[i]=0;//最后边界在再判断一次
//else st[i]=1; 不可以,可能会改变上面的判断 有一个不合法就不合法
}
//预处理2 判断第i-2列伸出来的和第i-1列伸出去的是否冲突
for(int i=0; i<1<<n; i++)
{
state[i].clear();
for(int j=0; j<1<<n; j++)
if((i&j)==0 && st[i|j]) // 交集为空 && 并集合法 --不能重合 空出来的连续偶数
state[i].push_back(j);
}
// dp
memset(f, 0, sizeof f);
f[0][0]=1;
// 第1 2维枚举
for(int i=1; i<=m; i++) //m列
for(int j=0; j<1<<n; j++) // 遍历列的每一个状态
for(auto k : state[j]) // 遍历第i-1列的状态k,如果“真正”可行,就转移
f[i][j]+=f[i-1][k];
cout<<f[m][0]<<endl; // 0开始到m-1 f[m][0]相当于m-1列都没伸出 即答案 m最大为11,故N+1=12
}
return 0;
}
最短Hamilton路径
[91. 最短Hamilton路径 - AcWing题库]:
/*
1.那些点被用过
2.目前停在哪些点上
f[i][j]:所有从0走到j 并且走过所有的点的情况是i 的所有路径集合
i的状态:走过为1 没走过为0
f[state][j] = f[state_k][k] + w[k][j] 0->j = 0->k + w[j][k]
state_k = state 除掉j之后的集合 并且state要包含j
注意:C++位运算的优先级比加减乘除的优先级低,所以遇到位运算和加减乘除一起的,要加个括号。
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 20, M = 1<<N;
int n, w[N][N], 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=1; i<1<<n; i+=2)
//循环时路径一定会从0开始,这样外层循环时间会减半 0位为1 一定是奇数
// 第1 2维枚举
for(int i=1; i< 1<<n; i++)
for(int j=0; j<n; j++)
if(i >> j & 1)
for(int k=0; k<n; k++)
if(i >> k & 1)
f[i][j]=min(f[i][j], f[i-(1<<j)][k] + w[j][k]);
cout<<f[(1<<n)-1][n-1];
return 0;
}
小国王
/*
f[i,j,st]
集合:所有只摆在前i行,已经摆了j个国王,并且第i行摆放的状态是st的所有方案的集合
属性:Count
状态计算: f[i-1][j-count(a)][b]
已经摆完i-1排,i-1排的状态是b,已经摆了j-count(a)个国王的所有方案
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 12, M = 1<<10;
int n, m;
ll f[N][N*N][M];
int cnt[M];
vector<int> st; //表示所有合法的状态
vector<int> head[M];//这个是每一状态所有可以转移到的其他状态
int check(int st) //判断一行中是否有两个相邻的国王
{
if((st & st>>1)== 0) return 1;
else return 0;
}
int count(int st) //计算状态二进制里面1的个数,即国王的个数
{
int res=0;
for(int i=0; i<n; i++) res+=st>>i&1;
return res;
}
int main()
{
cin>>n>>m;
// 预处理
for(int i=0; i<1<<n; i++) //i是状态
if(check(i)) //这一行状态合法
{
st.push_back(i);
cnt[i]=count(i); //合法状态中国王的数量
}
for(int i=0; i<st.size(); i++)
for(int j=0; j<st.size(); j++)
{
int a=st[i], b=st[j];
if((a&b)==0 && check(a|b)) //并集为空 && 交集不相邻
head[i].push_back(j); //a->b, b->a
}
// dp
f[0][0][0]=1;
// 第1 2 3维枚举
for(int i=1; i<=n+1; i++)
for(int j=0; j<=m; j++) //国王的数量
for(int a=0; a<st.size(); a++)//枚举所有状态
for(int b:head[a])//枚举所有a能到的状态 能到a 即可以互相转化的状态
{
int c=cnt[st[a]];
if(c<=j) //状态a中 国王的数量比j小的时候才合法
f[i][j][a]+=f[i-1][j-c][b]; // b->a
}
//前n行摆放了m个国王,并且第n行状态是0(没有国王)的所有方案数。 n+1=11 故N=12
cout<<f[n+1][m][0]; //等价于下面的
/*
ll res=0;
for(int i=0; i<st.size(); i++) res+=f[n][m][i];
cout<<res;
*/
return 0;
}
玉米田
/*
f[i,s]
集合:所有只摆在前i行,并且第i行摆放的状态是s的所有摆放方案的集合
属性:Count
1.二进制不包含两个连续的1
2.(a & b) == 0 没有交集
已经摆完前i行, 且第i行的状态是a,第i-1行的状态是b的所有方案
已经摆完前i-1行,且第i-1行的状态是b的所有摆放方案 f[i-1][b]
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 14, M = 1<<N, mod = 1e8;
int n, m;
int g[N]; //每一行空地的状态
ll f[N][M];
vector<int> st;
vector<int> head[M];
int check(int st) //判断一行中是否有两个相邻的国王
{
for(int i=0; i<m; i++) //m列 即m位
if((st>>i & 1) && (st>>i+1 & 1))
return 0;
return 1;
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++)
for(int j=0; j<m; j++)
{
int x; cin>>x;
g[i] += !x<<j; //0 坏地 存进去1 后面方面&运算
}
// 预处理1
for(int i=0; i<1<<m; i++)
if(check(i))
st.push_back(i);
// 预处理2
for(int i=0; i<st.size(); i++)
for(int j=0; j<st.size(); j++)
{
int a=st[i], b=st[j];
if((a&b)==0) // &优先级低 要加()
head[i].push_back(j);
}
// dp
f[0][0]=1;
for(int i=1; i<=n+1; i++)
for(int a=0; a<st.size(); a++)
for(int b : head[a])
{
if(g[i] & st[a]) continue; //与坏地不能有交集
f[i][a] = (f[i][a] + f[i-1][b]) % mod;
}
cout<<f[n+1][0];
return 0;
}
炮兵阵地
/*
f[i,j,k]
集合:所有只摆在前i行,并且第i-1行摆放的状态是j, 第i行摆放的状态是k 的所有摆放方案的最大值
属性:Max
当前第i行和i-1行的状态只依赖于第i-2行的状态 枚举第i-2行的状态
1.二进制不包含两个连续的1
2.(a & b) == 0 没有交集
check() 相邻两个1之间至少隔两个0
a b c没有交集 a&b | a&c | b&c == 0
i-1行 i行 分别于a b没有交集 g[i-1]&a | g[i]&b == 0
&1 二进制优化
1e8的数组需要400MB
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 110, M = 1<<10, mod = 1e8;
int n, m;
int g[N];
int cnt[M];
int f[2][M][M];
vector<int> st;
int check(int st) //判断一行中是否有两个相邻的国王
{
for(int i=0; i<m; i++) //m列 即m位
if((st>>i&1) && ((st>>i+1 & 1) || (st>>i+2&1)))
return 0;
return 1;
}
int count(int st)
{
int res=0;
for(int i=0; i<m; i++)
if(st>>i&1)
res++;
return res;
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++)
for(int j=0; j<m; j++)
{
char x; cin>>x;
if(x=='H')
g[i] += 1<<j;
}
// 预处理 找出所有合法状态,并计算每个状态1的个数
for(int i=0; i<1<<m; i++)
if(check(i))
{
st.push_back(i);
cnt[i]=count(i);
}
// dp
for(int i=1; i<=n+2; i++)
for(int j=0; j<st.size(); j++) //j:i-1行
for(int k=0; k<st.size(); k++)//k:i行
for(int u=0; u<st.size(); u++) //u:i-2行
{
int a=st[j], b=st[k], c=st[u]; // a:i-1 b:i c:i-2
if(a&b | a&c |b&c) continue;
if(g[i-1]&a | g[i]&b) continue;
f[i&1][j][k] = max(f[i&1][j][k], f[i-1&1][u][j] + cnt[b]);
}
cout<<f[n+2&1][0][0];
/*
int res=0;
for(int i=0; i<st.size(); i++)
for(int j=0; j<st.size(); j++)
res=max(res, f[n&1][i][j]);
cout<<res;
*/
return 0;
}
愤怒的小鸟
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
#define x first
#define y second
typedef long long ll;
typedef pair<double, double> pdd;
const int N = 18, M = 1<<18;
const double eps=1e-8;
int n, m;
pdd q[N];
int path[N][N];
int f[M];
int cmp(double x, double y)
{
if(fabs(x-y)<eps) return 0;
if(x<y) return -1;
return 1;
}
int main()
{
int t; cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=0; i<n; i++) cin>>q[i].x>>q[i].y;
memset(path, 0, sizeof path);
for(int i=0; i<n; i++)
{
path[i][i]=1<<i;
for(int j=0; j<n; j++)
{
double x1=q[i].x, y1=q[i].y;
double x2=q[j].x, y2=q[j].y;
if(!cmp(x1, x2)) continue;
double a = (y1/x1-y2/x2)/(x1-x2);
double b = y1/x1-a*x1;
if(cmp(a, 0)>=0) continue;
int state=0;
for(int k=0; k<n; k++)
{
double x=q[k].x, y=q[k].y;
if(!cmp(a*x*x+b*x, y)) state+=1<<k;
}
path[i][j]=state;
}
}
memset(f, 0x3f, sizeof f);
f[0]=0;
for(int i=0; i+1 < 1<<n; i++)
{
int x=0;
for(int j=0; j<n; j++)
if(!(i>>j&1))
{
x=j;
break;
}
for(int j=0; j<n; j++)
f[i|path[x][j]] = min(f[i|path[x][j]], f[i]+1);
}
cout << f[( 1 << n) -1]<<endl;
}
return 0;
}
区间DP
主要问题
- 环形区间--->链状
- 区间DP+记录方案数
- 区间DP+高精度
- 二维区间DP
石子合并
/*
f[i][j] 将 i到j 这一段石子合并成一堆的方案的集合
属性 Min
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int n, s[N];
int f[N][N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
{
cin>>s[i];
s[i]+=s[i-1]; //处理前缀和
}
for(int len=2; len<=n; len++) //枚举区间长度
for(int i=1; i+len-1<=n; i++) //枚举区间左端点 区间起点
{
int j=i+len-1; //右端点 区间终点
f[i][j]=0x3f3f3f3f;
for(int k=i; k<j; k++)
f[i][j]=min(f[i][j], f[i][k]+f[k+1][j] + s[j]-s[i-1]);
}
cout<<f[1][n];
return 0;
}
环形石子合并
/*
长度为n的环 -> 长度为2n的链
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 410, inf = 0x3f3f3f3f;
int n, w[N], s[N];
int f[N][N], g[N][N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
{
cin>>w[i];
w[i+n]=w[i];
}
for(int i=1; i<=2*n; i++) s[i]=s[i-1]+w[i];
memset(f, 0x3f, sizeof f);
memset(g, -0x3f, sizeof g);
for(int len=1; len<=n; len++) //区间长度
for(int i=1; i+len-1<=n*2; i++) //左端点 注意长度为2n
{
int j=i+len-1;
if(len==1) f[i][j]=g[i][j]=0; //右端点
else
for(int k=i; k<j; k++) //中间点
{
f[i][j]=min(f[i][j], f[i][k]+f[k+1][j] + s[j]-s[i-1]);
g[i][j]=max(g[i][j], g[i][k]+g[k+1][j] + s[j]-s[i-1]);
}
}
int minv=inf, maxv=-inf;
for(int i=1; i<=n; i++)
{
minv=min(minv, f[i][i+n-1]);
maxv=max(maxv, g[i][i+n-1]);
}
cout<<minv<<endl<<maxv;
return 0;
}
能量项链
/*
f[l][r]:所有将[l,r]合并成一个珠子的方式
注意长度为n+1 边界共用
f[i][j]->f[i][k]+f[k][j]+w[i]*w[k]*w[j];
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 210;
int n, w[N], f[N][N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++) cin>>w[i], w[i+n]=w[i];
for(int len=3; len<=n+1; len++) // 3---n+1
for(int l=1; l+len-1<=2*n; l++)
{
int r=l+len-1;
for(int k=l+1; k<r; k++) //l+1---r-1
f[l][r]=max(f[l][r], f[l][k]+f[k][r]+w[l]*w[k]*w[r]);
}
int res=0;
for(int i=1; i<=n; i++) res=max(res, f[i][i+n]); //i---n
cout<<res;
return 0;`
}
凸多边形的划分
/*
f[l,r]
集合:所有将[l,l+1], [l+1, l+2],...,[r-1,r], [r,l] 这个多边形划分成三角形的方案
属性:Min ---要赋初始值
状态计算:f[l][r] = max(f[l][r], f[l][k]+f[k][r]+w[l]*w[r]*w[k]) 环形
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 55, M = 35;
int n, w[N];
ll f[N][N][M]; //第三维按位存数
ll temp[M]; // cpy数组存数
//高精度加法
void add(ll a[], ll b[])
{
static ll c[M];
memset(c, 0, sizeof c);
ll t=0;
for(int i=0; i<M; i++)
{
t+=a[i]+b[i];
c[i]=t%10;
t/=10;
}
memcpy(a, c, sizeof c);
}
//高精度乘法
void mul(ll a[], ll b)
{
static ll c[M]; //相当于开了个全局变量,每次不用重新分配空间
memset(c, 0, sizeof c);
ll t=0;
for(int i=0; i<M; i++)
{
t+=a[i]*b;
c[i]=t%10;
t/=10;
}
memcpy(a, c, sizeof c);
}
//高精度按位比较
int cmp(ll a[], ll b[])
{
for(int i=M-1; i>=0; i--)
if(a[i]>b[i]) return 1;
else if(a[i]<b[i]) return -1;
return 0;
}
//高精度按位输出
void print(ll a[])
{
int k=M-1;
while(k&&!a[k]) k--;
while(k>=0) cout<<a[k--];
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++) cin>>w[i];
for(int len=3; len<=n; len++)
for(int l=1; l+len-1<=n; l++)
{
int r=l+len-1;
f[l][r][M-1]=1;
for(int k=l+1; k<r; k++) // l+1---r-1
{
memset(temp, 0, sizeof temp);
temp[0]=w[l];
mul(temp, w[k]);
mul(temp, w[r]);
add(temp, f[l][k]);
add(temp, f[k][r]);
if(cmp(f[l][r], temp)>0)
memcpy(f[l][r], temp, sizeof temp);
}
}
print(f[1][n]);
return 0;
}
加分二叉树
/*
f[l,r] 所有中序遍历是[l,r]这一段的二叉树的集合
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int n, w[N];
int f[N][N];
int g[N][N]; //记录[l,r]区间内的根结点
void dfs(int l, int r)
{
if(l>r) return ;
int k=g[l][r];
cout<<k<<" "; //前序遍历,根-左-右
dfs(l, k-1);
dfs(k+1, r);
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++) cin>>w[i];
for(int len=1; len<=n; len++)
for(int l=1; l+len-1<=n; l++)
{
int r=l+len-1;
if(len==1) f[l][r]=w[l], g[l][r]=l; //叶节点 权值和根都是是本身
else
{
for(int k=l; k<=r; k++)
{
int left = k == l ? 1 :f[l][k-1]; //判断左右子树是否为空
int right = k == r ? 1 : f[k+1][r];
int s = left * right + w[k];
if(f[l][r]<s) f[l][r]=s, g[l][r]=k;
}
}
}
cout<<f[1][n]<<endl;
dfs(1, n);
return 0;
}
棋盘分割
/*
f[x1,x2,y1,y2,k]
集合:将子矩阵(x1,y1) (x2,y2)切分成k部分的所有方案
当前已经对棋盘进行了k次划分,且k次划分后选择的棋盘是 左上角为(x1,y1) 右下角为(x2,y2)
属性:Min
划分出来的 k+1个子矩阵的nσ2最小
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 16;
int n, m=8;
int s[N][N];
double f[N][N][N][N][N];
double X;
double get(int x1, int y1, int x2, int y2)
{
double sum=s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1] - X;
return sum*sum/n;
}
double dp(int x1, int y1, int x2, int y2, int k)
{
double &v=f[x1][y1][x2][y2][k];
if(v>=0) return v;
if(k==1) return v=get(x1,y1,x2,y2);
v=1e9;
//横着切
for(int i=x1; i<x2; i++)
{
v=min(v, dp(x1,y1,i,y2,k-1) + get(i+1,y1,x2,y2));
v=min(v, dp(i+1,y1,x2,y2,k-1) + get(x1,y1,i,y2));
}
//竖着切
for(int i=y1; i<y2; i++)
{
v=min(v, dp(x1,y1,x2,i,k-1) + get(x1,i+1,x2,y2));
v=min(v, dp(x1,i+1,x2,y2,k-1) + get(x1,y1,x2,i));
}
return v;
}
int main()
{
cin>>n;
for(int i=1; i<=m; i++)
for(int j=1; j<=m; j++)
{
cin>>s[i][j];
s[i][j]+=s[i-1][j] + s[i][j-1] - s[i-1][j-1];
}
X=(double)s[m][m]/n;
memset(f, -1, sizeof f);
printf("%.3lf", sqrt(dp(1,1,8,8,n)));
return 0;
}
树形DP
链式前向星
int h[N]; //head 邻接表存储图,有n个节点,所以需要n个头节点 存储以 i 为起点的 最后1条边 的下标
int e[M]; //表示第i条边的终点
int ne[M]; //next 表示与第i条边同起点的 上 一条边的下标
int w[M]; //存边长
int idx;
void add(int a,int b,int c) //a到b点的距离为c
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
memset(h,-1,sizeof h);
for(int i=h[t];i!=-1;i=ne[i]) // t为父节点 找与t相连的边 按照存储顺序的倒序
int j=e[i];
a b e[idx]=b ne[idx]=h[a] h[a]=idx++
1 2 e[0] = 2; ne[0] = -1; h[1] = 0;
1 3 e[1] = 3; ne[1] = 0; h[1] = 1;
1 5 e[2] = 5; ne[2] = 1; h[1] = 2;
2 4 e[3] = 4; ne[3] = -1; h[2] = 3;
没有上司的舞会
/*
集合:f[u][0] 所有从以u为根的子树中选择,并且不选u这个点的方案
f[u][1] 所有从以u为根的子树中选择,并且选u这个点的方案
*/
#include <bits/stdc++.h>
using namespace std;
const int N=6010, M=2*N;
int n, ha[N];
int h[N], e[M], ne[M], idx;
int fa[N], f[N][2];
void add(int a, int b)
{
e[idx]=b, ne[idx]=h[a], h[a]=idx++;
}
void dfs(int u)
{
f[u][1]=ha[u]; //当前选u,需要加上u的幸福度
for(int i=h[u]; i!=-1; i=ne[i])
{
int j=e[i];
dfs(j); //回溯,用该值开始新遍历
f[u][1]+=f[j][0]; //选u,不选子树
f[u][0]+=max(f[j][0], f[j][1]); //不选u,子树可选可不选
}
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++) cin>>ha[i];
memset(h, -1, sizeof h);
for(int i=0; i<n-1; i++)
{
int a,b;
cin>>a>>b;
add(b, a); //b父节点->a子节点
fa[a]=1; //标记子节点
}
int root=1;
while(fa[root]) root++; //找根节点
dfs(root);
cout<<max(f[root][0],f[root][1]);
}
计数类DP
整数划分
/*
方法1 类似完全背包
f[i,j] i是体积 j是总容量
集合: 从1~i中选,恰好是j的集合
f[i,j] = f[i-1,j] + f[i-1, j-i] + ... + f[i-1, j-si]
f[i,j -i] = f[i-1,j-i] + f[i-1, j-2i] + ... + f[i-1, j-si]
f[i,j] = f[i-1,j] + f[i,j -i]
*/
#include <bits/stdc++.h>
using namespace std;
const int N=1010, mod=1e9+7;
int n,f[N];
int main()
{
cin>>n;
f[0]=1;
for(int i=1; i<=n; i++)
for(int j=i; j<=n; j++)
f[j]=(f[j]+f[j-i])%mod;
cout<<f[n];
return 0;
}
/*
方法2
f[i,j]
集合:所有总和是i,并且恰好表示成j个数和的方案
属性:count
状态计算:
最小值等于1 | 最小值大于1
f[i-1][j-1] | f[i-j][j] (每个数都-1)
*/
#include <bits/stdc++.h>
using namespace std;
const int N=1010, mod=1e9+7;
int n,f[N][N];
int main()
{
cin>>n;
f[0][0]=1;
for(int i=1; i<=n; i++)
for(int j=1; j<=i; j++) //和<=i个1
f[i][j]=(f[i-1][j-1]+f[i-j][j])%mod;
int res=0;
for(int i=1; i<=n; i++) res=(res+f[n][i]) %mod;
cout<<res;
return 0;
}
数位DP
计数问题
线性DP
数字三角形
#include <bits/stdc++.h>
using namespace std;
const int N=510, INF=1e9;
int n,a[N][N],f[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=1; i<=n; i++)
for(int j=0; j<=i+1; j++) //初始化时注意j的边界 0~—i+1;
f[i][j]=-INF; // 下一行最右边的的会用到上一行的右边
f[1][1]=a[1][1];
for(int i=1; i<=n; i++)
for(int j=1; j<=i; j++)
f[i][j]=max(f[i-1][j-1], f[i-1][j])+a[i][j];
int res=-INF;
for(int i=1; i<=n; i++)
res=max(res, f[n][i]); //取最后一行的最大值
cout<<res;
return 0;
}
// 倒序不用考虑边界问题
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int f[N][N];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>f[i][j];
}
}
for(int i=n;i>=1;i--){
for(int j=i;j>=1;j--){
f[i][j]=max(f[i+1][j],f[i+1][j+1])+f[i][j];
}
}
cout<<f[1][1]<<endl;
}
摘花生
#include <bits/stdc++.h>
using namespace std;
int t,m,n;
int a[110][110], f[110][110];
int main()
{
cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
cin>>a[i][j];
//dp[i][j]=0; 每次都从头跑一遍,更新过了,不影响
}
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
f[i][j]=max(f[i-1][j], f[i][j-1]) + a[i][j];
cout<<f[n][m]<<endl;
}
}
// 滚动数组优化
#include<cstring>
#include<iostream>
using namespace std;
const int N = 105;
int a[2][N], f[2][N], q, n, m;
int main()
{
cin >> q;
while(q--){
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i&1][j];
f[i&1][j] = max(f[i&1][j-1], f[(i-1)&1][j]) + a[i&1][j];
}
}
cout << f[n&1][m] << endl;
memset(f, 0, sizeof f);
}
}
最低通行费
#include <bits/stdc++.h>
using namespace std;
int n;
int a[110][110], f[110][110];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
cin>>a[i][j];
//需要先把所有状态初始化为正无穷,初始化状态的起点(dp求最小值必须要的步骤)
//状态转移时的越界判断
memset(f, 0x3f, sizeof f);
//法1
f[1][1]=a[1][1];
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
f[i][j]=min(f[i][j], f[i-1][j]+a[i][j]);
f[i][j]=min(f[i][j], f[i][j-1]+a[i][j]);
//f[i][j]=min(f[i-1][j], f[i][j-1]) +a[i][j];
//这样写f[0][1]和f[1][0]会被考虑 都是最大值
}
//法2
f[0][1]=f[1][0]=0; //边界初始化为0
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
f[i][j]=min(f[i-1][j], f[i][j-1]) +a[i][j];
cout<<f[n][n];
}
方格取数
#include <bits/stdc++.h>
using namespace std;
int n;
int w[11][11], f[21][11][11];
int main()
{
cin>>n;
int a,b,c;
while( cin>>a>>b>>c, a||b||c)
w[a][b]=c;
//f[k][i1][i2]
for(int k=2; k<=2*n; k++) //k表示i+j 即行走的步数
for(int i1=1; i1<=n; i1++)
for(int i2=1; i2<=n; i2++)
{
int j1=k-i1, j2=k-i2;
if(j1>=1&&j1<=n && j2>=1&&j2<=n) //保证j合法
{
int &x=f[k][i1][i2]; //引用 地址一样 x随着f改变而改变
int t=w[i1][j1];
if(i1!=i2) t+=w[i2][j2]; //重合的时候加一次 不重合两个都加
x=max(x, f[k-1][i1-1][i2-1]+t); //i1-1,j1 i2-1,j2
x=max(x, f[k-1][i1-1][i2]+t); //i1-1,j1 i2,j2-1
x=max(x, f[k-1][i1][i2-1]+t); //i1,j1-1 i2-1,j2
x=max(x, f[k-1][i1][i2]+t); //i1,j1-1 i2,j2-1
}
}
cout<<f[2*n][n][n];
return 0;
}
最长上升子序列
//集合: 所有以第i个数结尾的上升子序列
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,a[N],f[N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++)
{
f[i]=1;//只有a[i]一个数 为1
for(int j=1; j<i; j++)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);// 此处f[i]不能改为1
}
int res=0;
for(int i=1; i<=n; i++)
res=max(f[i],res);
cout<<res;
return 0;
}
最长上升子序列 II (贪心+二分)
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, a[N], q[N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
q[0]=-2e9;
int len=0;
for(int i=1; i<=n; i++)
{
int l=0, r=len;
while(l<r)
{
int mid=r+l+1>>1;
if(q[mid]<a[i]) l=mid;
else r=mid-1;
}
q[r+1]=a[i];
len=max(len, r+1);
}
cout<<len;
return 0;
}
最大上升子序列和
将 改成 即可
//集合: 所有以第i个数结尾的上升子序列
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,a[N],f[N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++)
{
f[i]=a[i];//只有a[i]一个数 为1
for(int j=1; j<i; j++)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+a[i]);// 此处f[i]不能改为1
}
int res=0;
for(int i=1; i<=n; i++)
res=max(f[i],res);
cout<<res;
return 0;
}
拦截导弹
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n=0, a[N], f1[N], f2[N];
int main()
{
int x;
while(cin>>x) a[++n]=x;
//for(int i=0; i<=n; i++) cout<<a[i]<<endl;
int res1=0;
int res2=0;
for(int i=1; i<=n; i++)
{
f1[i]=1;
f2[i]=1;
for(int j=1; j<i; j++)
if(a[i]<=a[j])
f1[i]=max(f1[i], f1[j]+1); //res1=max(res1, f1[i]);
else
f2[i]=max(f2[i], f2[j]+1);// res2=max(res2, f2[i]);
res1=max(res1, f1[i]);
res2=max(res2, f2[i]);
//不能写在里面, 只有一个数的时候res不会更新为0,应该为1
}
cout<<res1<<endl<<res2;
return 0;
}
导弹防御系统
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, a[N], up[N], down[N];
int ans;
//前u个导弹 上升序列个数su 下降序列个数sd
void dfs(int u, int su, int sd)
{
if(su+sd>=ans) return; //剪枝
if(u==n)
{
ans=su+sd;
return;
}
//情况1:考虑用上升拦截系统来拦截第u个导弹
//将当前数放在上升子序列中
//如果当前已有的上升拦截系统的高度都大于第u个导弹高度,则重新开一套系统
//否则,则由当前低于第u个导弹最高拦截系统来负责拦截
int k=0;
while(k<su && up[k]>a[u]) k++; //找到了有这么个拦截系统,即上升子序列
int t=up[k]; //t记录下up[k] 用于dfs回溯的时候恢复现场
up[k]=a[u];
if(k<su) dfs(u+1, su, sd); //放在当前序列
else dfs(u+1, su+1, sd); //新开一个序列
up[k]=t; //恢复现场
//情况2:考虑用下降拦截系统来拦截第u个导弹
//将当前数放在下降子序列中
k=0;
while(k<sd && down[k]<a[u]) k++;
t=down[k];
down[k]=a[u];
if(k<sd) dfs(u+1, su, sd);
else dfs(u+1, su, sd+1);
down[k]=t;
}
int main()
{
while(cin>>n && n)
{
for(int i=0; i<n; i++) cin>>a[i];
ans=n;
dfs(0, 0, 0);
cout<<ans<<endl;
}
return 0;
}
最长公共子序列
//集合:所有在 第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现 的子序列
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m, f[N][N];
char a[N], b[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];
return 0;
}
最长公共上升子序列
/*
f[i][j]:所有由第一个序列的前i个字母,第二个序列的前j个字母构成的
且以b[j]结尾的公共上升子序列
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
int n, a[N], b[N];
int f[N][N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1; i<=n; i++) cin>>b[i];
for(int i=1; i<=n; i++)
{
int maxv=1;
for(int j=1; j<=n; j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j]) f[i][j]=max(f[i][j], maxv);
if(a[i]>b[j]) maxv=max(maxv, f[i-1][j]+1);
}
}
int res=0;
for(int i=1; i<=n; i++) res=max(res, f[n][i]);
cout<<res;
return 0;
}
/* 优化前会超时
每次循环求得的maxv是满足 a[i]>b[k] 的f[i-1][k]+1的前缀最大值。
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];
if (a[i] == b[j])
{
int maxv = 1;
for (int k = 1; k < j; k ++ )
if (a[i] > b[k])
maxv = max(maxv, f[i - 1][k] + 1);
f[i][j] = max(f[i][j], maxv);
}
}
}
*/
最短编辑距离
//集合:所有将a[1~i]变成b[1~j]的操作方式
#include <bits/stdc++.h>
using namespace std;
const int N=15, M=1010;
int n,m,f[N][N];
char str[M][N];
int dis(char a[],char b[])
{
int la=strlen(a+1), lb=strlen(b+1);
for(int i=0; i<=la; i++) f[i][0]=i;
for(int i=0; i<=lb; i++) f[0][i]=i;
for(int i=1; i<=la; i++)
for(int j=1; j<=lb; j++)
{
f[i][j]=min(f[i-1][j]+1, f[i][j-1]+1);
f[i][j]=min(f[i][j], f[i-1][j-1] + (a[i]!=b[j]) );
}
return f[la][lb];
}
int main()
{
cin>>n>>m;
for(int i=0; i<n; i++) cin>>str[i]+1;
while(m--)
{
char s[N];
int limit;
cin>>s+1>>limit;
int res=0;
for(int i=0; i<n; i++)
if( dis(str[i],s) <= limit)
res++;
cout<<res<<endl;
}
return 0;
}
编辑距离
//集合:所有将a[1~i]变成b[1~j]的操作方式
#include <bits/stdc++.h>
using namespace std;
const int N=15, M=1010;
int n,m,f[N][N];
char str[M][N];
int dis(char a[],char b[])
{
int la=strlen(a+1), lb=strlen(b+1);
for(int i=0; i<=la; i++) f[i][0]=i;
for(int i=0; i<=lb; i++) f[0][i]=i;
for(int i=1; i<=la; i++)
for(int j=1; j<=lb; j++)
{
f[i][j]=min(f[i-1][j]+1, f[i][j-1]+1);
f[i][j]=min(f[i][j], f[i-1][j-1] + (a[i]!=b[j]) );
}
return f[la][lb];
}
int main()
{
cin>>n>>m;
for(int i=0; i<n; i++) cin>>str[i]+1;
while(m--)
{
char s[N];
int limit;
cin>>s+1>>limit;
int res=0;
for(int i=0; i<n; i++)
if( dis(str[i],s) <= limit)
res++;
cout<<res<<endl;
}
return 0;
}
怪盗基德的滑翔翼
#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n, a[N], f[N];
int main()
{
int t; cin>>t;
while(t--)
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
int res=0;
////向左最长上升
//正向求解 往左边跳 从左到右最长上升
for(int i=1; i<=n; i++)
{
f[i]=1;
for(int j=1; j<i; j++)
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
res=max(res, f[i]);
}
//向右最长下降
//正向求解 往右边跳 从右到左最长上升
for(int i=n; i>0; i--)
{
f[i]=1;
for(int j=n; j>i; j--)
if(a[i]>a[j])
f[i]=max(f[i], f[j]+1);
res=max(res, f[i]);
}
/*
//或者从左向右最长下降子序列
for(int i=1; i<=n; i++)
{
f[i]=1;
for(int j=1; j<i; j++)
if(a[i]<a[j])
f[i]=max(f[i],f[j]+1);
res=max(res, f[i]);
}
*/
cout<<res<<endl;
}
return 0;
}
登山
类似上题
不能正向求最长下降子序列!!! 如果直接把正向结果与最长上升序列长度相加, 得到的是 从前往后以为结尾的最长上升序列长度 从前往后以为结尾的最长下降序列长度 而题目所求的可以转化为 从前往后以为结尾的最长上升长度 从后往前以]为结尾的最长上升长度。两者是不同的。
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n, a[N], f1[N], f2[N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++)
{
f1[i]=1;
for(int j=1; j<i; j++)
if(a[i]>a[j])
f1[i]=max(f1[i],f1[j]+1);
}
for(int i=n; i>0; i--)
{
f2[i]=1;
for(int j=n; j>i; j--)
if(a[i]>a[j])
f2[i]=max(f2[i], f2[j]+1);
}
int res=0;
for(int i=1; i<=n; i++) //左右相加
res=max(res, f1[i]+f2[i]-1);
cout<<res<<endl;
return 0;
}
合唱队形
类似上题
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n, a[N], f1[N], f2[N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++)
{
f1[i]=1;
for(int j=1; j<i; j++)
if(a[i]>a[j])
f1[i]=max(f1[i],f1[j]+1);
}
for(int i=n; i>0; i--)
{
f2[i]=1;
for(int j=n; j>i; j--)
if(a[i]>a[j])
f2[i]=max(f2[i], f2[j]+1);
}
int res=0;
for(int i=1; i<=n; i++) //左右相加
res=max(res, f1[i]+f2[i]-1);
cout<<n-res<<endl; //最少出列=总数-保留的最长的
return 0;
}
友好城市
#include <bits/stdc++.h>
using namespace std;
const int N=5010;
int n, a[N], f[N];
struct node {
int x, y;
}s[N];
bool cmp(node a, node b)
{
return a.x<b.x;
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>s[i].x>>s[i].y;
sort(s+1, s+n+1, cmp);
//一岸从小到大排序,另一岸求最大上升子序列
//保存下来另一岸的坐标
for(int i=1; i<=n; i++)
a[i]=s[i].y;
for(int i=1; i<=n; i++)
{
f[i]=1;
for(int j=1; j<i; j++)
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
/*
若j从0开始 就不用把f[i]=1,下面会更新
for(int i=1; i<=n; i++)
for(int j=0; j<i; j++)
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
*/
int res=0;
for(int i=1; i<=n; i++)
res=max(res, f[i]);
cout<<res;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N=5010;
int n, a[N], f[N];
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
int res=0;
for(int i=1; i<=n; i++)
{
f[i]=a[i]; //这里不是1
for(int j=1; j<i; j++)
if(a[i]>a[j])
f[i]=max(f[i],f[j]+a[i]);
res=max(res, f[i]);
}
/*
for(int i=1; i<=n; i++)
{
for(int j=0; j<i; j++) //从0开始就不用初始化,利用f[0]=0会在下面进行初始化
if(a[i]>a[j])
f[i]=max(f[i],f[j]+a[i]);
res=max(res, f[i]);
}
*/
cout<<res;
return 0;
}
杨老师的照相排列
//集合:所有将a[1~i]变成b[1~j]的操作方式
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n, m, f[N][N];
char a[N], b[N];
int main()
{
cin>>n>>a+1;
cin>>m>>b+1;
for(int i=0; i<=m; i++) f[0][i]=i;
for(int i=0; i<=n; i++) f[i][0]=i;//初始化
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
/*
f[i][j]=min(f[i-1][j]+1, f[i][j-1]+1); //插入 删除
if(a[i]==b[j]) f[i][j]=min(f[i][j], f[i-1][j-1]); //替换
else f[i][j]=min(f[i][j], f[i-1][j-1]+1);
*/
// 两中均可以 下面慢点
if(a[i]==b[j]) f[i][j]=f[i-1][j-1];
else f[i][j]=min({f[i-1][j]+1, f[i][j-1]+1, f[i-1][j-1]+1});
}
cout<<f[n][m];
return 0;
}