CSP-S突破营day4
## DP
### 入门题
dp 三要素: 状态、 转移方程、 初始化条件。
状态:
转移方程:描述状态与状态之间的关系的式子。
初始化条件:没有办法用其他状态求出来的值的状态怎么求(边界)。
##### Problem 1
思路:
易得转移方程为
代码1(用别人的值求自己的值):
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int f[maxn];
//用别人的值求自己的值
int main(){
cin.tie(0) -> sync_with_stdio(0);
int n;
cin >> n;
f[0] = 0, f[1] = 1;
for(int i = 2;i <= n;i++){
f[i] = f[i - 1] + f[i - 2];
}
cout << f[n] << '\n';
return 0;
}
```
代码2(用自己的值求别人的值):
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int f[maxn];
//用自己的值求别人的值
int main(){
cin.tie(0) -> sync_with_stdio(0);
int n;
cin >> n;
f[0] = 0, f[1] = 1;
for(int i = 0;i <= n;i++){
f[i + 1] += f[i];
f[i + 2] += f[i];
}
cout << f[n] << '\n';
return 0;
}
```
##### Problem 1.5
给出
易得转移方程为
```cpp
#include <iostream>
using namespace std;
const int maxn = 1e4 + 10;
int f[maxn][maxn];
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
}
}
cout << f[n][m] << '\n';
return 0;
}
```
-----
##### Problem 2
易得:
```cpp
#include <iostream>
using namespace std;
const int maxn = 1e4 + 10;
int a[maxn][maxn], f[maxn][maxn];
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
cin >> a[i][j];
}
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
f[i][j] = min(f[i][j - 1], f[i - 1][j]) + a[i][j];
}
}
cout << f[n][m] << '\n';
return 0;
}
```
----
##### Problem 3
# [USACO1.5] [IOI1994]数字三角形 Number Triangles
## 题目描述
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

在上面的样例中,从
易得:
代码:
```cpp
#include <iostream>
#include <queue>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cstdio>
#include <set>
#include <map>
#include <unordered_map>
#include <bitset>
using namespace std;
const int MAXX = 1e5 + 10, MAXN = 1e9 + 10;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[2500][2500];
int f[2500][2500];//f[i][j]代表走到第i行地j列所能得到的和的最大值
int main(){
int n = read();
memset(f, 0, sizeof(f));
for(int i = 1;i <= n;i++){
for(int j = 1;j <= i;j++){
a[i][j] = read();//读入数字三角形
}
}
f[1][1] = a[1][1];//初始化
for(int i = 2;i <= n;i++){
for(int j = 1;j <= i;j++){
f[i][j] = max(f[i - 1][j], f[i - 1][j - 1]) + a[i][j];//左上角和上面的最大值+自己的值
}
}
int ans = f[n][1];
for(int i = 1;i <= n;i++){
ans = max(ans, f[n][i]);
}
cout << ans << '\n';
return 0;
}
```
----
##### Problem 4
在上一个题的基础上,变化为求和
考虑加一维状态,用

代码:
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
bool f[111][111][111];//f[i][j][k]表示走到第i行第j列使得%100=k这件事是否可能
int a[maxn][maxn];
int main(){
cin.tie(0) -> sync_with_stdio(0);
int n;
cin >> n;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= i;j++){
cin >> a[i][j];
}
}
f[1][1][a[1][1] % 100] = true;
for(int i = 1;i < n;i++){
for(int j = 1;j <= i;j++){
for(int k = 0;k < 100;k++){
if(f[i][j][k]){//可能
f[i + 1][j][(k + a[i + 1][j]) % 100] = true;
f[i + 1][j + 1][(k + a[i + 1][j + 1]) % 100] = true;
}
}
}
}
int ans = -1;
for(int i = 1;i <= n;i++){
for(int k = 0;k < 100;k++){
if(f[n][i][k]){
ans = max(ans, k);
}
}
}
cout << ans << '\n';
return 0;
}
```
-----
##### Problem 5
最长上升子序列求数量、方案、多少种不同的最长上升子序列。
**动态规划复杂度

数量:
代码:
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n;
int a[maxn], f[maxn];//f[i]代表以a[i]结尾的最长上升子序列
int main(){
cin.tie(0) -> sync_with_stdio(0);
cin >> n;
for(int i = 1;i <= n;i++){
cin >> a[i];
}
for(int i = 1;i <= n;i++){
f[i] = 1;
for(int j = 1;j < i;j++){//要从a[i]前面找一个比a[i]小的数a[j]
if(a[j] < a[i]){
if(f[j] + 1 > f[i]){
f[i] = f[j] + 1;
}
}
}
}
int ans = -1;
for(int i = 1;i <= n;i++){
ans = max(ans, f[i]);
}
cout << ans << '\n';
return 0;
}
```
方案:
代表:
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n;
int a[maxn], f[maxn];//f[i]代表以a[i]结尾的最长上升子序列
int g[maxn];//g[i]代表f[i]是从f[g[i]]转移来
void print(int p){
if(p == 0){
return ;
}
print(g[p]);
cout << a[p] << '\n';
}
int main(){
cin.tie(0) -> sync_with_stdio(0);
cin >> n;
for(int i = 1;i <= n;i++){
cin >> a[i];
}
for(int i = 1;i <= n;i++){
f[i] = 1;
for(int j = 1;j < i;j++){//要从a[i]前面找一个比a[i]小的数a[j]
if(a[j] < a[i]){
if(f[j] + 1 > f[i]){
f[i] = f[j] + 1;
g[i] = j;//代表f[i]是从f[j]转移来
}
}
}
}
int ans = -1;
for(int i = 2;i <= n;i++){
if(f[i] > f[ans]){
ans = i;
}
}
print(ans);//输出f[p]所对应的解
return 0;
}
```
多少种不同的最长上升子序列:
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
int a[maxn];;
int f[maxn];//f[i]代表以a[i]结尾的最长上升子序列长度
int g[maxn];//g[i]代表f[i]是从 f[g[i]]转移过来的
int h[maxn];//h[i]代表达成f[i]总共有多少种方案
int n;
void print(int p)
{
if (p==0) return;
print(g[p]);
cout << a[p] << "\n";
}
int main()
{
cin >> n;
for (int i=1;i<=n;i++)
cin >> a[i];
//动态规划复杂度 = 枚举状态复杂度 * 枚举转移的复杂度
for (int i=1;i<=n;i++)//要计算f[i]的值
{//O(n)枚举状态
f[i] = 1; h[i] = 1;
for (int j=1;j<i;j++)//要从a[i]前面找一个比a[i]小的数a[j]
//O(n)枚举转移
if (a[j] < a[i])
{
if (f[j] + 1 > f[i])
{
f[i] = f[j] + 1;
g[i] = j;//f[i]是从f[j]转移过来的
h[i] = h[j];
}
else if (f[j] + 1 == f[i]) h[i] += h[j];
}
}
int p=1;
for (int i=2;i<=n;i++)
if (f[i] > f[p]) p=i;
print(p);//输出f[p]所对应的解
return 0;
}
```
##### Problem 6
乌龟棋,长度为

代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
int f[45][45][45][45];
int g[5];
int a[400];
int main()
{
int n,m;
cin>>n>>m;
int i,j,k,l;
for(i=1;i<=n;i++)
cin>>a[i];
f[0][0][0][0]=a[1];
for(i=1;i<=m;i++){
int x;
cin>>x;
g[x]++;
}
for(i=0;i<=g[1];i++)
for(j=0;j<=g[2];j++)
for(k=0;k<=g[3];k++)
for(l=0;l<=g[4];l++){
int move=1+i+2*j+3*k+4*l;
if(i!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l]+a[move]);
if(j!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l]+a[move]);
if(k!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k-1][l]+a[move]);
if(l!=0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k][l-1]+a[move]);
}
cout<<f[g[1]][g[2]][g[3]][g[4]]<<endl;
return 0;
}
```
----
### 背包
##### Problem 1
```cpp
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4 + 5;
int n,m;
int f[maxn][maxn];
int w[maxn],v[maxn];
int main()
{
cin >> m >> n;
for (int i=1;i<=n;i++)
cin >> v[i] >> w[i];
for (int i=1;i<=n;i++)
for (int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];
if (j>=v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
}
int ans=0;
for (int i=0;i<=m;i++)
ans=max(f[n][i],ans);
cout << ans << "\n";
return 0;
}
```
```cpp
# include <iostream>
using namespace std;
int w[1145],c[1145],f[1145][1145];
int main(){
int n,m; cin >> m >> n;
for (int i = 1;i <= n;i++) cin >> w[i] >> c[i];
for (int i = 1;i <= n;i++){
for (int v = m;v > 0;v--){
if (w[i] <= v) f[i][v] = max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
else f[i][v] = f[i-1][v];
}
}cout << f[n][m];
return 0;
}
```
-----
#### 01背包
即选与不选。
##### Problem 1
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10, oo = 0x3f3f3f3f;
//01背包
int f[maxn];//f[i]表示用了i的体积最多获得多少价值
int main(){
cin.tie(0) -> sync_with_stdio(0);
for(int i = 0;i <= m;i++){
f[i] = -oo;
}
f[0] = 0;
for(int i = 1;i <= n;i++){
for(j = v[i];j <= m;j++){
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
return 0;
}
```
##### Problem 2
物品有
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int f[101][1000];//f[i][j]表示前i个物品 用来j的体积 最多获得多少价值
int w[maxn], v[maxn];//v[i]表示物品体积,w[i]表示价值
int k[maxn];//代表物品的个数
int n, m;
int main(){
cin.tie(0) -> sync_with_stdio(0);
cin >> n >> m;
for(int i = 1;i <= m;i++){
cin >> v[i] >> w[i] >> k[i];
}
for(int i = 1;i <= m;i++){
for(int j = n;j >= 0;j--){//要求f[i][j]这个状态
for(int k = 0;k * v[i] <= k && k <= ::k[i];k++){
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + w[i] * k);
}
}
}
int ans = 0;
for(int i = 0;i <= m;i++){
ans = max(f[n][i], ans);
}
cout << ans << '\n';
return 0;
}
```
#### 有限背包
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int f[101][1000];//f[i][j]表示前i个物品 用来j的体积 最多获得多少价值
int w[maxn], v[maxn];//v[i]表示物品体积,w[i]表示价值
int r, n, m;
int main(){
cin.tie(0) -> sync_with_stdio(0);
cin >> r >> m;//O(nlog m)
for(int i = 1;i <= m;i++){
int a, b, c;
cin >> a >> b >> c;
int k = 1;
while(c >= k){//还可以拆k个出来
n++;
v[n] = a * k;
w[n] = b * k;
c -= k;
k *= 2;
}
if(c != 0){
n++;
v[n] = a * c;
w[n] = b * c;
}
}
return 0;
}
```
----
### 数位 dp
- 按照数字的位数划分转移阶段
- 转移方式:枚举下一位数字填什么
- 限制条件:数位的上下界要求
##### Problem 1
从
~~直接输出
转化为
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int z[101];//z[i]代表x的第i位是多少
int f[101][2];//f[i][j]代表已经填完y_n~y_i的情况下j=0代表y<x j=1代表y=x 此时有多少种方案
int dp(int x){//求0~x有几个数
int n = 0;//x有几位
while(x != 0){
n++;
z[n] = x % 10;
x /= 10;
}
memset(f, 0, sizeof(f));
f[n + 1][1] = 1;
for(int i = n;i >= 1;i--){//当前要填y[i]
for(int j = 0;j < 2;j++){//要从f[i+1][j]这个状态进行转移
int up = 9;//y[i]这一位最大能填多少
if(j == 1){
up = z[i];
}
for(int k = 0;k <= up;k++){//y[i]要填k
f[i][(k == up) && (j == 1)] += f[i + 1][j];
}
}
}
return f[1][0] + f[1][1];
}
int main(){
cin.tie(0) -> sync_with_stdio(0);
int l, r;
cin >> l >> r;
cout << dp(r) - dp(l - 1) << '\n';
return 0;
}
```
----
##### Problem 2
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int z[101];//z[i]代表x的第i位是多少
int f[101][2];//f[i][j]代表已经填完y_n~y_i的情况下j=0代表y<x j=1代表y=x 此时有多少种方案
int g[105][2];//g[i][j]代表已经填完y_n~y_i的情况下j=0代表y<x j=1代表y=x 此时所有方案的数位之和
int dp(int x){//求0~x有几个数
int n = 0;//x有几位
while(x != 0){
n++;
z[n] = x % 10;
x /= 10;
}
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
f[n + 1][1] = 1;
for(int i = n;i >= 1;i--){//当前要填y[i]
for(int j = 0;j < 2;j++){//要从f[i+1][j]这个状态进行转移
int up = 9;//y[i]这一位最大能填多少
if(j == 1){
up = z[i];
}
for(int k = 0;k <= up;k++){//y[i]要填k
f[i][(k == up) && (j == 1)] += f[i + 1][j];
g[i][(k == up) && (j == 1)] += k * f[i + 1][j] + g[i + 1][j];
}
}
}
return g[1][0] + g[1][1];
}
int main(){
cin.tie(0) -> sync_with_stdio(0);
int l, r;
cin >> l >> r;
cout << dp(r) - dp(l - 1) << '\n';
return 0;
}
```
---
##### Problem 3
求在
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int z[101];//z[i]代表x的第i位是多少
int f[101][2][11];//f[i][j][k]代表已经填完y_n~y_i的情况下j=0代表y<x j=1代表y=x 且最后一位是k 此时有多少种方案
int dp(int x){//求0~x有几个数
int n = 0;//x有几位
while(x != 0){
n++;
z[n] = x % 10;
x /= 10;
}
memset(f, 0, sizeof(f));
for(int i = 1;i <= n;i++){//y有几位
int up = 9;
if(i == n){
up = z[i];
}
for(int j = 1;j <= up;j++){//y[i]这一位填j
f[i][(i == n) && (j == z[i])][j]++;
}
}
for(int i = n;i >= 1;i--){//当前要填y[i]
for(int j = 0;j < 2;j++){
for(int k = 0;k < 10;k++){//要从f[i+1][j][k]这个状态进行转移
int up = 9;//y[i]这一位最大能填多少
if(j == 1){
up = z[i];
}
for(int r =0;r <= up;r++){//y[i]要填r
if(abs(r - k) >= 2){
f[i][(j == 1) && (r == up)][r] += f[i + 1][j][k];
}
}
}
}
}
int ans = 0;
for(int i = 0;i < 2;i++){
for(int j = 0;j < 10;j++){
ans += f[1][i][j];
}
}
return ans;
}
int main(){
cin.tie(0) -> sync_with_stdio(0);
int l, r;
cin >> l >> r;
cout << dp(r) - dp(l - 1) << '\n';
return 0;
}
```
----
##### Problem 4
求在
用
可以考虑

```cpp
#include<bits/stdc++.h>
using namespace std;
int z[30];
long long f[20][2][65][46][33][26];
long long dp(long long x, long long k)
//求0~x中 有多少个数重要度为k
{
int n=0;
while (x)
{
n++;
z[n] = x % 10;
x/=10;
}
if (k!=0)
{
long long k_=k;
int a=0,b=0,c=0,d=0;
while (k%2==0)
a++,k/=2;
while (k%3==0)
b++,k/=3;
while (k%5==0)
c++,k/=5;
while (k%7==0)
d++,k/=7;
if (k!=1) return 0;
memset(f,0,sizeof(f));
f[n+1][1][0][0][0][0]=1;
for (int i=n;i>=1;i--)//要填y[i]
for (int j=0;j<2;j++)
for (int n2=0;n2<=a;n2++)
for (int n3=0;n3<=b;n3++)
for (int n5=0;n5<=c;n5++)
for (int n7=0;n7<=d;n7++)//f[i+1][j][n2][n3][n5][n7]
{
int up = 9;
if (j==1) up=z[i];
for (int r=1;r<=up;r++)
{
int m2=n2,m3=n3,m5=n5,m7=n7;
if (r==2) m2++;
if (r==3) m3++;
if (r==4) m2+=2;
if (r==5) m5++;
if (r==6) m2++,m3++;
if (r==7) m7++;
if (r==8) m2+=3;
if (r==9) m3+=2;
f[i][(j==1) && (r==up)][m2][m3][m5][m7] += f[i+1][j][n2][n3][n5][n7];
}
}
return f[1][0][a][b][c][d] + f[1][1][a][b][c][d];
}
else//k==0 填的位中要有一位=0
{
}
}
int main()
{
long long l,r,k;
cin >> l >> r >> k;
cout << dp(r,k) - dp(l-1,k) << "\n";
return 0;
}
```
-----
##### Problem 5
房间里放着
经典的“旅行商问题”(TSP,Travelling Salesman Problem),即从一个点出发,经过所有的点并返回原点,要求最小化总代价(或距离)。TSP 是一个 NP-hard 问题,意味着对于较大的输入,无法在多项式时间内找到精确解法。
复杂度是
```cpp
#include<bits/stdc++.h>
using namespace std;
const int maxn=18;
const double oo=1e20;
double x[maxn],y[maxn];
double dis(int i,int j){
if(i==-1) return sqrt((0-x[j])*(0-x[j])+(0-y[j])*(0-y[j]));
return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
double f[1<<maxn][maxn], a[16][16];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>x[i]>>y[i];
}
for(int i=0;i<(1<<n);i++){
for(int j=0;j<=n;j++){
f[i][j]=oo;
}
}
x[0] = 0, y[0] = 0;
for(int i=0;i<=n;i++){
for(int j=i+1;j<=n;j++){
a[i][j]=a[j][i]=dis(i,j);
}
}
for(int i=1;i<=n;i++){
f[1<<(i-1)][i]=a[0][i];
}
// f[1][0]=0.0;
for(int s=0;s<(1<<n);s++){
for(int i=1;i<=n;i++){
if(s>>(i-1)&1)//保证合法
{
for(int j=1;j<=n;j++){
if(i!=j&&(s>>(j-1)&1)==0)
{
f[s|(1<<(j-1))][j]=min(f[s|(1<<(j-1))][j],f[s][i]+dis(i,j));
}
}
}
}
}
double ans=oo;
for(int i=1;i<=n;i++){
ans=min(ans,f[(1<<n)-1][i]);
}
printf("%.2lf\n",ans);
return 0;
}
```
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!