背包问题
01背包问题
#include<iostream>
using namespace std;
const int maxn=1010;
int n,m;
int f[maxn],v[maxn],w[maxn];
int main()
{
cin >> n >> m;
for(int i=1; i<=n; i++)
{
cin >> v[i] >> w[i];
}
for(int i=1; i<=n; i++)
for(int j=m; j>=v[i]; j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout << f[m];
return 0;
}
完全背包问题
#include<iostream>
using namespace std;
const int maxn=1111;
int f[maxn],w[maxn],v[maxn];
int n,m;
int main()
{
cin >> n >> m;
for(int i=1; i<=n; i++)
{
cin >> v[i] >> w[i];
}
for(int i=1; i<=n; i++)
{
for(int j=v[i]; j<=m; j++) //区别于01背包,这里就是从小到大排列
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout << f[m];
return 0;
}
多重背包问题01
#include<iostream>
using namespace std;
const int maxn=111;
int n,m;
int f[maxn][maxn],v[maxn],w[maxn],s[maxn];
int main()
{
cin >> n >> m;
for(int i=1; i<=n; i++)
cin >> v[i] >> w[i] >> s[i];
for(int i=1; i<=n; i++)
for(int j=0; j<=m; j++)
for(int k=0; k<=s[i] && j>=v[i]*k; k++)
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
cout << f[n][m];
return 0;
}
多重背包问题02
#include<iostream>
using namespace std;
const int N=12010; //0<N<1000,0<s<2000,所以看下面的循环n*log2000就约等于12000
const int M=2010; //M只用给f,所以最大体积就是f[M]
int n,m;
int f[M],w[N],v[N];
int main()
{
cin >> n >> m;
int cnt=0; //cnt从0开始,相当于是将这些值全部分开
for(int i=1; i<=n; i++)
{
int a,b,s;
cin >> a >> b >> s;
int k=1;
while(k<=s)
{
cnt++; //cnt表示
v[cnt]=k*a;
w[cnt]=k*b;
s-=k; //先是让s-=k,最后再更新k的值
k*=2;
}
if(s>0) //如果s还有剩余
{
cnt++; //cnt还是++,进行一个分组
v[cnt]=s*a;
w[cnt]=s*b;
}
}
n=cnt; //将n更新为cnt
//才用01背包的方法
for(int i=1; i<=n; i++)
for(int j=m; j>=v[i]; j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout << f[m];
return 0;
}
分组背包问题
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) //和01背包的区别在于,这里 进行了一个分组
{
cin >> s[i];
for (int j = 0; j < s[i]; j ++ )
cin >> v[i][j] >> w[i][j];
}
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 0; j -- ) //这里j的值不是>=v[i],而是j>=0,因为此时v[i][k]表示第i组,第k个
for (int k = 0; k < s[i]; k ++ )
if (v[i][k] <= j)
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
cout << f[m] << endl; //m最后就是总体积
return 0;
}
线性DP
数字三角形
#include<iostream>
using namespace std;
const int maxn=511;
int n;
int f[maxn][maxn];
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-1; i>=1; i--)
for(int j=1; j<=n; j++)
f[i][j]+=max(f[i+1][j],f[i+1][j+1]);
cout << f[1][1];
return 0;
}
最长上升子序列
#include<iostream>
using namespace std;
const int maxn=1111;
int a[maxn],f[maxn]; //a表示给出的数组,f表示到达每个点的最长子序列
int main()
{
int n;
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++) //从头开始遍历,一直到i之前
if(a[j]<a[i]) //如果a[j]<a[i],则可以将第i个数放到序列中,则f[i]为f[j]+1
f[i]=max(f[i],f[j]+1);
}
int res=-n;
for(int i=1; i<=n; i++) //最后找出所有点的最大长度即可
res=max(res,f[i]);
cout << res;
return 0;
}
最长上升子序列2
思路分析
这道题本身是将数据范围给大了,所以要考虑简便算法
1.对于 3 1 2 1 8 5 6 对于8而言,可以插入到3后面,也可以插入到1后面,但是我们选择插入到1的后面,因为范围更大,所以插入到3的后面就多余了,对于朴素算法而言,我们需要将8插入到前面的所有比他小的值后面,然后找出最大长度
2.注意q[i] 表示长度是i的上升子序列最后一个数的最小值,q[i]是随着i的值递增的
3.对于a[i]而言,我们肯定是要将a[i]放到前面的一个最大长度,并且该最大长度的最后一个值要小于a[i],要找长度i,所以找q[i],所以我们采用2分的思路,在q[]中找出小于a[i]的最大值,此时找到的这个位置比如q[4],表示长度为4是之前的最大长度,并且长度为4的上升子序列中的最小值为q[4],并且q[4]是小于a[i]的最大值,也就是q[5]的值>=a[i],表示长度为5的上升子序列的最后一个值的最小值是>=a[i],由于q的属性,则q[5]=a[i]
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int q[N]; //q存放的是,每种长度的上升子序列的结尾的值最小是多少,其实这里随着长度的增加,结尾的值也在递增,呈一个上升趋势
int main()
{
scanf("%d", &n); //输入n
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]); //输入a[i]
int len = 0;
for (int i = 0; i < n; i ++ )
{ //数字a[i]可以接到任何一个比自己小的值的后面,如果想要长度最大,则需要将a[i]接到比a[i]小的最大值的后面
int l = 0, r = len;
while (l < r) //通过2分,找出小于a[i]的最大的数,这个二分也是在q中去寻找一个合适的长度,
{
int mid = l + r +1 >> 1;
if (q[mid] < a[i]) l = mid; //中间这个值q[mid]小于a[i],现在要找到小于a[i]的最大值,所以从该值开始一直往后找,则让l=mid,正常的思路如果是要找后半部分,则需要l=mid+1,但是我们现在是l=mid,则mid=l+r+1>>1
else r = mid-1;
}
len = max(len, r + 1);
q[r + 1] = a[i];
}
printf("%d\n", len);
return 0;
}
最长公共子序列
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int main()
{
scanf("%d%d", &n, &m); //输入n,m
scanf("%s%s", a + 1, b + 1); //a,b均为字符串数组,其中加1表示从下标为1开始
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]);
if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
}
printf("%d\n", f[n][m]);
return 0;
}
最短编辑距离
石子合并
整数划分
该问题可以看成容量为n的一个背包,一共有n个体积为1,2,3,4…n的物品,相当于是问将背包装满一共有多少种装法,并且每个物品可以有n中选择
体积升序排列
#include<iostream>
using namespace std;
const int maxn=1010;
const int mod=1e9+7;
int n;
int f[maxn];
int main()
{
cin >> n;
f[0]=1;
for(int i=1; i<=n; i++) //相当于是从1到i,
for(int j=i; j<=n; j++) //此时体积也是从i到n
f[j]=(f[j]+f[j-i])%mod;
cout << f[n];
return 0;
}
计数问题
计数问题
#include<iostream>
#include<vector>
using namespace std;
const int maxn=101010;
int get(vector<int> num,int l,int r)
{
int res=0;
for(int i=l; i>=r; i--)
res=res*10+num[i];
return res;
}
int power10(int x)
{
int res=1;
for(int i=0; i<x; i++)
res*=10;
return res;
}
int count(int n, int x) //在n中,统计x出现的个数
{
if (!n) return 0; //如果n为0,直接返回0
vector<int> num; //num存放n中每一位的个数
while (n) //其实这个num是逆着存放n的位数
{
num.push_back(n % 10);
n /= 10;
}
n = num.size();
int res = 0; //res表示每一位中出现的个数
for (int i = n - 1 - !x; i >= 0; i -- ) //i应该是从倒数第一位开始的,如果要统计的数x为0,则从倒数第2位开始,i>=0
{
if (i < n - 1) //如果i从倒数第2位开始,也就是正数第1位
{
res += get(num, n - 1, i + 1) * power10(i); //
if (!x) res -= power10(i); //如果要统计的数为0,则少掉了10^i个
}
if (num[i] == x) res += get(num, i - 1, 0) + 1; //如果当前位置上的这个数等于x,则后面一共有000~efg,共efg+1个
else if (num[i] > x) res += power10(i); //如果当前位置的数>x,则值为000--999,共10^i个
}
return res;
}
int main()
{
int a, b;
while (cin >> a >> b , a) //输入a,b,其中a为0,则循环结束
{
if (a > b) swap(a, b); //如果a>b,则交换,保证第1个数小,第2个数大
for (int i = 0; i <= 9; i ++ )
//注意是a-1
cout << count(b, i) - count(a - 1, i) << ' '; //统计a--b之间,0~9中所有数的个数,就是统计b中0~9所有数的个数再减去a中0~9中所有数的个数
cout << endl; //换行
}
return 0;
}
蒙德里安的梦想
最短Hamilton路径
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 20, M = 1 << N; //注意N,M的设置
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进行初始化为最大
f[1][0] = 0; //注意这里f[1][0]设为0,注意这个初始值
for (int i = 0; i < 1 << n; i ++ ) //i就是 邹精
for (int j = 0; j < n; j ++ ) //j就是0~n
if (i >> j & 1) //i要把j收拾下哈
for (int k = 0; k < n; k ++ ) //k也是从0--n
if (i >> k & 1) //i把k也收拾一哈
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]); //注意加括号
cout << f[(1 << n) - 1][n - 1];
return 0;
}
没有上司的舞会
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 6010;
int n;
int h[N], e[N], ne[N], idx;
int happy[N];
int f[N][2];
bool has_fa[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
f[u][1] = happy[u]; //对于选择节点u来说,其幸福指数为happy[u]
for (int i = h[u]; i!=-1; i = ne[i])
{
int j = e[i];
dfs(j);
f[u][1] += f[j][0]; //u是j的父亲节点,选择u就不能选择j
f[u][0] += max(f[j][0], f[j][1]); //不选择u,则选择其孩子节点的最值
}
}
int main()
{
scanf("%d", &n); //输入n个节点,输入每个人的幸福指数
for (int i = 1; i <= n; i ++ ) scanf("%d", &happy[i]);
memset(h, -1, sizeof h); //对h初始化
for (int i = 0; i < n - 1; i ++ )
{
int a, b;
scanf("%d%d", &a, &b); //输入a,b节点
add(b, a); //b是a的父亲节点
has_fa[a] = true; //标记a有父亲节点
}
int root = 1;
while (has_fa[root]) root ++ ; //找到根节点
dfs(root); //深搜
printf("%d\n", max(f[root][0], f[root][1]));
return 0;
}
滑雪
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n, m;
int g[N][N];
int f[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //左上右下
int dp(int x, int y)
{
int &v = f[x][y]; //通过&v,后面对v的值的修改直接作用于f[x][y]
if (v != -1) return v; //如果v!=-1,表示之前已经访问过了
v = 1; //v的最小值为1,因为最起码从该点出发还能有一个点
for (int i = 0; i < 4; i ++ ) //一共遍历4个方向
{
int a = x + dx[i], b = y + dy[i];
if (a >= 1 && a <= n && b >= 1 && b <= m && g[x][y] > g[a][b]) //必须在范围内,且该点的高度大于下个点
v = max(v, dp(a, b) + 1); //通过递归的方式
}
return v;
}
int main()
{
scanf("%d%d", &n, &m); //输入n行,m列
for (int i = 1; i <= n; i ++ ) //一共是n行,m列
for (int j = 1; j <= m; j ++ )
scanf("%d", &g[i][j]); //g表示每个点的高度
memset(f, -1, sizeof f); //对f初始化,全部为-1
int res = 0; //res表示所经过的区域
for (int i = 1; i <= n; i ++ ) //共n个点,
for (int j = 1; j <= m; j ++ )
res = max(res, dp(i, j)); //找出最大的通过的区域数
printf("%d\n", res); //输出
return 0;
}