动态规划
- 确定递推状态: f(n)+解释
- 确定递推公式
- 程序实现
优化:
- 去除冗余状态
- 状态重定义
- 优化转移过程
- 斜率优化
优化-递归+记忆化
if arr[n] return arr[n]
递归+记忆化(正向求解-递归) 或 改变求解顺序(逆向递归求解-循环) : 解决效率问题
用long long ,或者更大的:解决数值溢出,不够大问题
HZOJ 38 兔子繁殖问题
- 确定递推状态: f(n)为第n个月的时候兔子的数量
- 确定递推公式: f(1)=1 | f(2)=2 | f(n)=f(n-1)+f(n-2)
- 程序实现
#include <iostream>
using namespace std;
#define MAX_N 100
int digui_jiyihua_arr[MAX_N+5]={0};int f(int n){if(n<=2) return n;if(digui_jiyihua_arr[n])return digui_jiyihua_arr[n]; //优化-增加记忆点,解决超时问题digui_jiyihua_arr[n]=f(n-1)+f(n-2);return digui_jiyihua_arr[n];
}
void acm_1_14_digui_jiyihua_test(){int n;cin>>n;cout << f(n) <<endl;
}
改变求解顺序
从低到高
long long acm_1_14_digui_jiyihua_f[105]={0};
void acm_1_14_digui_jiyihua_test2(){int n;cin>>n;acm_1_14_digui_jiyihua_f[1]=1;acm_1_14_digui_jiyihua_f[2]=2;for(int i=3;i<=n;i++){acm_1_14_digui_jiyihua_f[i]=acm_1_14_digui_jiyihua_f[i-1]+acm_1_14_digui_jiyihua_f[i-2];}cout << acm_1_14_digui_jiyihua_f[n] <<endl;
}
//
// Created by cry on 2024/3/24.
//
#include <vector>
#include <iostream>
using namespace std;
#define MAX_N 100
long long digui_jiyihua_arr[MAX_N+5]={0};long long f(int n){if(n<=2) return n;if(digui_jiyihua_arr[n])return digui_jiyihua_arr[n]; //优化-增加记忆点,解决超时问题digui_jiyihua_arr[n]=f(n-1)+f(n-2);return digui_jiyihua_arr[n];
}
void acm_1_14_digui_jiyihua_test(){int n;cin>>n;cout << f(n) <<endl;
}
//大整数
class BigInt:
public vector<int>{public:BigInt(){push_back(0);}BigInt(int x){this->push_back(x);process_digit();//处理大整数进位问题}//重载+=运算BigInt &operator+=(const BigInt &a){for(int i=0;i<a.size();i++){if(i >= size()) //如果位数超出了{push_back(a[i]);}else at(i)+=a[i];}process_digit();return *this;}//重载加法运算BigInt operator+(const BigInt &a){BigInt ret(*this);//拷贝一下当前ret+=a;return ret;}//重载左移运算符//进位void process_digit() {for (int i = 0; i < size(); i++) {if (at(i) < 10) continue; //当前位置的值小于10,不需要处理//当前位置的值大于10,就进位if (i == size() - 1) push_back(0); //增加一位at(i + 1) += at(i) / 10;at(i) %= 10;}return ;}
};
// 重写左移运算符
ostream &operator<<(ostream &out,const BigInt &a){for(int i=a.size()-1;i>=0;i--){out << a[i];}return out;
}
BigInt acm_1_14_digui_jiyihua_f[105]={0};
void acm_1_14_digui_jiyihua_test2(){int n;cin>>n;acm_1_14_digui_jiyihua_f[1]=1;acm_1_14_digui_jiyihua_f[2]=2;for(int i=3;i<=n;i++){acm_1_14_digui_jiyihua_f[i]=acm_1_14_digui_jiyihua_f[i-1]+acm_1_14_digui_jiyihua_f[i-2];}cout << acm_1_14_digui_jiyihua_f[n] <<endl;
}
int main() {acm_1_14_digui_jiyihua_test2();return 0;
}
python解法 只需要记忆化就行了,类型不会出问题
#第一代,缺点:效率低 且 数据类型不够大
# dp_1(65) 直接跑不完
def dp_1(n):if(n<=2):return nreturn dp_1(n-1)+dp_1(n-2)
# 解决:添加记忆化
MAX_N=100
arr=[0]*(MAX_N+5)
def dp_2(n):if(n<=2):return nif arr[n]:return arr[n]arr[n]=dp_2(n-1)+dp_2(n-2)return arr[n]
n=int(input())
print(dp_2(n))
# 改变求解顺序
def dp_3(n):arr=[0]*(MAX_N+5)if(n<=2):return narr[1]=1arr[2]=2for i in range(3,n+1):arr[i]=arr[i-1]+arr[i-2]return arr[n]
print(dp_3(n))
容斥原理的基本思想
- 在计数的时候为了没有重复 没有遗漏
- 再不考虑重叠的情况,把包含于某内容的所有对象数目先计算出来,然后把计数时重复的计算的数目排斥出去
加多的减掉,减多了加回来
- 结果既无遗漏又无重复—容斥原理
钱币问题
- 确定递推状态:
f[i][j]
表示用i种钱币,凑足j元钱的方法总数1 2 5 f(2)(5) ,前两种钱币凑5块有
1 2 2
1 1 1 2
1 1 1 1 1
三种
- 确定递推公式
拆分:分成两个不相交的子集
a. 没有使用第i种钱币
b. 使用了第i种钱币
f[i][j]=f[i-1][j]+f[i][j-w[i]]
w:1 2 5
f(3)(5)=f(3-1)(5)+f(3)(5-5)
=f(2)(5)+f(3)(0)
=f(1)(5)+f(2)(3)+f(3)(0)
=f(1)(5)+f(1)(3)+f(2)(1)+f(3)(0)
=f(1)(5)+f(1)(3)+f(1)(1)+f(2)(0)+f(3)(0)
=f(0)(5)+f(0)(3)+f(0)(1)+f(1)(0)+f(2)(0)+f(3)(0)
=0+0+0+0+1+1+1
=3
- 程序实现
//
// Created by cry on 2024/3/27.
//
#include <iostream>using namespace std;
#define MAX_N 10000
#define MAX_M 20
int w[MAX_N+5];
int f[MAX_M+5][MAX_N+5]; //前m种钱币拼凑n元钱
void acm_1_14_dp_money_test(){int m,n;cin >> m>> n;for(int i=1;i<=m;i++) { //钱币类型遍历cin >> w[i];}for(int i=1;i<=m;i++){f[i][0]=1; //前i种钱币凑0元钱为1种// 因为f(2)(5)+f(3)(0)=f(3)(5)// 3+1=4,所以f(3)(0)为1for (int j = 1; j <= n; j++) {f[i][j]=f[i-1][j];if(j<w[i])continue; //小于第i种钱币的面额f[i][j]+=f[i][j-w[i]];f[i][j] %= 9973;}}cout << f[m][n] << endl;
}
使用python实现
def get_dp_dp_money(m,n,w):# f=[[0]*(n+5)]*(m+5) //同一个地址f=[[0]*(n+5) for _ in range(m+5)] # 不同地址for i in range(1,m+1):f[i][0]=1 # 终止条件for j in range(1,n+1):f[i][j]=f[i-1][j]if(j<w[i-1]):continue #f(1)(3)没有值,为0f[i][j]+=f[i][j-w[i-1]]f[i][j]%=9973print(f[m][n])if __name__=='__main__':m,n=list(map(int,input().split(" ")))w=list(map(int,input().split(" ")))w.append(0)get_dp_dp_money(m,n,w)
HZOJ40:爬楼梯
- 确定递推状态: f[n]表示n级的方法总数
1 2 5 f(2)(5) ,前两种钱币凑5块有
1 2 2
1 1 1 2
1 1 1 1 1
三种
- 确定递推公式
拆分:分成两个不相交的子集,且相加为全集
a. 最后一步跨两步:走到f[n-2]台阶的方法总数
b. 最后一步跨三步:走到f[n-3]台阶的方法总数
f[n]=f[n-2]+f[n-3]
- 程序实现
#include <iostream>
using namespace std;
#define MAX_N 500
int res[MAX_N+5];
void acm_1_14_dp_palouti_test(){int n;res[0]=1;res[1]=0;res[2]=1;cin >> n;for(int i=3;i<=n;i++){res[i]=res[i-2]+res[i-3];}cout <<res[n]<<endl;
}
python
def palouti():n=int(input())res=[0]*505res[1]=0res[0]=1res[2]=1;for i in range(3,n+1):res[i]=res[i-2]+res[i-3]print(res[n])palouti()
HZOJ 41:墙壁涂色
n块墙壁可以涂k种颜色
墙壁呈现环形,相邻两块墙壁不能相同
确定递推状态:
f[n][i][j]
:n块墙壁,首墙壁i种颜色,尾墙壁j种颜色的方案数确定递推公式
拆分:分成两个不相交的子集,且相加为全集
a. 最后一步跨两步:走到f[n-2]台阶的方法总数
b. 最后一步跨三步:走到f[n-3]台阶的方法总数
f[n][i][j]=f[n-1][i][k] 且k!=j 最后累加
: 首尾可能相同
f[n][i][j] 且 i!=j的项累加
:剔除首尾相同的边界条件:f[1]={1,0,0, 0,1,0 0,0,1}
- 程序实现
解决内存超界的方法:使用滚动数组
//
// Created by cry on 2024/3/27.
//
#include <iostream>
using namespace std;
#define MAX_N 1000
#define MAX_K 10
int ys[2][MAX_K+5][MAX_K+5]; //使用滚动数组解决内存超限问题
void acm_1_14_dp_qiangBiTuSe_test(){int n,k;cin >> n>>k; //读取数据:n块墙壁涂k种颜色for(int i=1;i<=k;i++){ys[1][i][i]=1;} //初始化,f[1] ,对角线元素为1,其余为0for(int ws=2;ws<=n;ws++){for(int i=1;i<=k;i++){for(int j=1;j<=k;j++){ys[ws %2][i][j]=0;for(int l=1;l<=k;l++){if(l==j){ continue;}ys[ws%2][i][j]+=ys[(ws-1)%2][i][l];}}}}//提出i==j的部分int ans=0;for(int i=1;i<=k;i++){for(int j=1;j<=k;j++){if(i==j){ continue;}ans+=ys[n%2][i][j];}}cout << ans << endl;}
python
#!/usr/bin/python3
# _*_ coding: utf-8 _*_
#
# Copyright (C) 2024 - 2024 Cry, Inc. All Rights Reserved
#
# @Time : 2024/3/27 15:03
# @Author : Cry
# @File : 墙壁涂颜色.py
# @IDE : PyCharmMAX_N = 1000
MAX_K = 10
# f = [[[0] * (MAX_K + 5)] * (MAX_K + 5)] * 2 # f 是一个包含两个元素的列表。每个元素都是一个包含 (MAX_K + 5) * (MAX_K + 5) 个元素的二维列表。这意味着 f[0] 和 f[1] 是两个指向内存中相同位置的引用,因此,如果你修改 f[0],f[1] 也会受到影响
f = [[[0] * (MAX_K + 5) for _ in range(MAX_K + 5)] for _ in range(2)]
#f 是一个包含两个元素的列表。每个元素都是一个包含 (MAX_K + 5) * (MAX_K + 5) 个元素的二维列表。但是,与第一行代码不同的是,这里的两个二维列表是独立的,它们并不共享内存中的相同位置。因此,如果你修改 f[0],f[1] 不会受到影响。def qiangbitumo():n, k = list(map(int, input().split(" ")))for i in range(1, k + 1):f[1][i][i] = 1 # 对角线元素为1for ws in range(2, n + 1):for i in range(1, k + 1):for j in range(1, k + 1):f[ws % 2][i][j] = 0for l in range(1, k + 1):if l == j:continuef[ws % 2][i][j] += f[(ws - 1) % 2][i][l];ans = 0for i in range(1, k + 1):for j in range(1, k + 1):if i == j:continueans += f[n % 2][i][j]print(ans)qiangbitumo()
洛谷P1025 数的划分
确定递推状态:
f(i)(j):数字i分成j份的方法总数
确定递推公式
拆分:分成两个不相交的子集,且相加为全集
a. 拆分的方案中有1的方案总数 :去掉1:剩下的总和为f(i-1)(j-1)
b. 拆分的方案中没有1的方案总数:每份都减去1,减j个1:f(i-j)(j)
递推公式:
f[i][j]=f[i-1][j-1]+f[i-j][j]
边界条件:
f[0][0]=1 f[i][1]=1
- 程序实现
学习:在固定份数的前提统计:全集分成两个集合:
按照有1和没有1,有1减1,没有1每份都减去1
//
#include <iostream>
using namespace std;
#define MAX_N 200
#define MAX_K 6
int f1[MAX_N+5][MAX_K+5];
void acm_1_14_dp_shudehuafen_test(){int n,k;cin >> n >> k;
// f(i)(j)=f(i-1)(j-1)+f(i-j)(j)f1[0][0]=1; //将0拆成0份,方法为1for(int i=1;i<=n;i++){
// 设置边界条件f1[i][1]=1; //将i拆成1份,拆成方法总数为1
// f[0][j] =0 f[i][1]=1for(int j=1,J=min(i,k);j<=J;j++){f1[i][j]=f1[i-1][j-1]+f1[i-j][j];}}cout << f1[n][k] <<endl;
}
int main(){acm_1_14_dp_shudehuafen_test();return 0;
}
python
# 定义最大值
MAX_N = 200
MAX_K = 6# 初始化动态规划数组
f1 = [[0] * (MAX_K + 5) for _ in range(MAX_N + 5)]def acm_1_14_dp_shudehuafen_test():# 输入 n 和 kn, k = map(int, input().split())# 初始化边界条件f1[0][0] = 1# 动态规划过程for i in range(1, n + 1): #求1到n的每个数的分成k份的份数f1[i][1] = 1 #分1份为1for j in range(1, min(i, k) + 1):f1[i][j] = f1[i - 1][j - 1] + f1[i - j][j]# 输出结果print(f1[n][k])acm_1_14_dp_shudehuafen_test()
洛谷1028 数的计算
确定递推状态:
f(i):以i开头的合法数列数量
确定递推公式
拆分:分成两个不相交的子集,且相加为全集
a. 不扩展:只有一个i 1个
b. 能扩展的,从i/2 i/2-1 i/2-2 … 1
递推公式:
f[i]=1+Σf[j] (j<=i/2)
边界条件:
f[1]=1 f[i][1]=1
- 程序实现
学习:在固定份数的前提统计:全集分成两个集合:
按照有1和没有1,有1减1,没有1每份都减去1
#include <iostream>
using namespace std;
#define MAX_N 1000
int f2[MAX_N+5]={0};
void acm_1_14_dp_shudejisuan_test(){int n;cin >> n;//边界条件f2[0]=0;for(int i=1;i<=n;i++){f2[i]=1;for(int j=1;j<=i/2;j++){f2[i]+=f2[j]; //f[i]=1+Σ f[i/2]}}cout << f2[n] << endl;
}
int main(){acm_1_14_dp_shudejisuan_test();return 0;
}
python
def getNum():n=int(input())f=[0]*1005for i in range(1,n+1):f[i]=1for j in range(1,i//2+1):f[i]+=f[j]print(f[i])
getNum()
洛谷P1038 神经网络
使用拓扑序列计算神经元
Ci=Σ(WijCj) - Ui
拓扑序用于AOV网中,拓扑序是先行关系的 序列,前面的神经元是后面神经元的先行活动。
拓扑序生成原理:
- 将入度为0的点输出(无前驱的活动)
- 将 上一步输出的节点所在的弧都删除
- 重复第一第二步
- 若输出顶点数< 有向涂顶点数,则有回路,否则输出的即为拓扑序列
//
// Created by cry on 2024/3/28.
//
#include <iostream>
#include <vector>
#include <queue>using namespace std;
#define MAX_N 100
//Ci=Σ(w3ijCj) - Ui
int c[MAX_N+5];//状态值 Ci
int u[MAX_N+5];//被减去的 Ui
int w3[MAX_N+5][MAX_N+5 ]; //wij
vector<int> g[MAX_N+5];//每个节点指向的节点
int outdeg[MAX_N+5];//出度
int indeg[MAX_N+5];//出度
void acm_1_14_dp_shenjingwangluo_test(){//读取数据int n,p;cin >>n >>p;//读取每个节点的c值和u值for( int i=1;i<=n;i++){cin >> c[i] >> u[i];//如果是输入层节点,直接为-u[i]if (c[i]==0)c[i]=-u[i];}//读取若干条边的值for(int i=0,a,b,W3;i<p;i++){cin >> a >> b >>W3;w3[a][b]=W3; //w3[i][j]indeg[b]+=1;//入度+1outdeg[a]+=1; //出度+1g[a].push_back(b); //添加出边}queue<int> q;//将入度为0的点都加入到队列,将输入层节点都加进去(入度为0)for(int i=1;i<=n;i++){if(indeg[i])continue;q.push(i);}//开始按照拓扑序依次计算每个序列的值while(!q.empty()){int ind=q.front(); //计算当前节点状态值q.pop();if(c[ind]>0){ //当前节点被激活for(int i=0;i<g[ind].size();i++){int to=g[ind][i];c[to]+=w3[ind][to] * c[ind];//将index权值传播给下一个节点}}//将下一层节点所有入度-1for (int i=0;i<g[ind].size();i++) {int to=g[ind][i];indeg[to]-=1;if(indeg[to]==0){q.push(to); //入度为0,添加到输出}}}int flag=0;//如果输出层都<=0,就输出NULLfor(int i=1;i<=n;i++){if(outdeg[i]) continue; //出度不为0 过if(c[i]<=0) continue; //未被激活 过cout << i<<" "<<c[i]<<endl;flag=1;}if(flag==0) cout <<"NULL" <<endl;}
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-MAX_N = 100c = [0] * (MAX_N + 5)
u = [0] * (MAX_N + 5)
w3 = [[0] * (MAX_N + 5) for _ in range(MAX_N + 5)] # wij
g = [[] for _ in range(MAX_N + 5)]
outdeg = [0] * (MAX_N + 5)
indeg = [0] * (MAX_N + 5)def acm_1_14_dp_shenjingwangluo_test():n, p = map(int, input().split())for i in range(1, n + 1):c[i], u[i] = map(int, input().split())if c[i] == 0:c[i] = -u[i]for i in range(p):a, b, w = map(int, input().split())w3[a][b] = w # w3[i][j]indeg[b] += 1 # 入度 +1outdeg[a] += 1 # 出度 +1g[a].append(b) q = []for i in range(1, n + 1):if indeg[i]:continueq.append(i)while q:ind = q.pop(0) if c[ind] > 0: for i in range(len(g[ind])):to = g[ind][i]c[to] += w3[ind][to] * c[ind]for i in range(len(g[ind])):to = g[ind][i]indeg[to] -= 1if indeg[to] == 0:q.append(to) flag = 0for i in range(1, n + 1):if outdeg[i]:continueif c[i] <= 0:continueprint(f"{i} {c[i]}")flag = 1if not flag:print("NULL")acm_1_14_dp_shenjingwangluo_test()
洛谷1044栈
1到n的栈的输出所有可能
定义递归状态
f[n]代表n个的合法方案数
推导递推公式
分割:出战数字结尾为n到1的方案的数量,然后加起来,结果为f[n]总和
最后一个出栈数字为x的时候条件:
- 小于x的数字进行入栈出栈
- 所有小于x的数字在x入栈之前出完栈
- x一直在栈底,比x大的数字一直入栈出栈
当x作为结尾的方案总数为:f(x-1) * f(n-x) :小于x的方案数量乘上大于x的方案数量
- 写代码
//
// Created by cry on 2024/3/28.
//
#include <iostream>
using namespace std;
#define MAX_N 18
int f_zhan[MAX_N+5];
void acm_1_14_dp_zhan_test(){int n;cin >> n;f_zhan[0]=1;for (int i=1;i<=n;i++) {f_zhan[i]=0;for(int x=1;x<=i;x++){f_zhan[i]+=f_zhan[x-1]*f_zhan[i-x];}}cout << f_zhan[n]<<endl;
}
int main(){acm_1_14_dp_zhan_test();return 0;
}
python
def zhan():n=int(input())f=[0]*20f[0]=1for i in range(1,n+1):f[i]=0for x in range(1,i+1):f[i]+=f[x-1]*f[i-x]print(f[n])
zhan()
洛谷1050 循环
打表:
输出y值
//
// Created by cry on 2024/3/28.
//
#include <iostream>
#include <string>
#include <vector>using namespace std;
//大整数
class BigInt:public vector<int>{
public:
// 构造函数BigInt(){push_back(0);}BigInt(int n,int v):vector<int>(n,v){}BigInt(int x){push_back(x);process_digit();return;
}BigInt(string &s,int k) {for(int i=0,j=s.size()-1;i<k;i++,j--){push_back(s[j]-'0');}return ;
}BigInt operator*(const BigInt &a){BigInt ret(min(MaxLen,int(size()+a.size()-1)),0);for(int i=0;i<size();i++){for(int j=0;j<a.size();j++){if(i+j >=MaxLen)continue; //超过最大长度ret[i+j]+=at(i)*a[j];}}ret.process_digit();return ret;}BigInt &operator *=(int x){for(int i=0;i<size();i++)at(i)*=x;process_digit();return *this;
}static int MaxLen;private:void process_digit(){ //进位for(int i=0;i<size();i++){if(at(i)<10)continue;if(i+1<MaxLen) {if (i + 1 == size()) push_back(0);at(i + 1) += at(i) / 10;}at(i) %= 10;}return ;
}
};int BigInt::MaxLen=0;
ostream &operator<<(ostream &out,const BigInt &a){for(int i=a.size()-1;i>=0;--i){out << a[i];}return out;
}
int acm_1_14_dp_xunhuan_test(){string s;int k;cin >> s>>k;BigInt::MaxLen=k;BigInt n(s,k);BigInt pre_y=n,y;vector<int> arr;for(int i=0;i<n.size();i++){y=pre_y;int cnt=1;while((y*n).at(i)!=n[i]){y=y*pre_y;cnt+=1;if(cnt ==11)break; //当前位置不存在循环节}if(cnt ==11){cout <<"-1" <<endl;return 0;}arr.push_back(cnt);pre_y=y;}BigInt ans=1;for(int i=0;i<arr.size();i++){ans *= arr[i];}cout << ans << endl;return 0;
}
int main() {acm_1_14_dp_xunhuan_test();return 0;
}
//32 2
//4at(i) %= 10;}return ;
}
};int BigInt::MaxLen=0;
ostream &operator<<(ostream &out,const BigInt &a){for(int i=a.size()-1;i>=0;--i){out << a[i];}return out;
}
int acm_1_14_dp_xunhuan_test(){string s;int k;cin >> s>>k;BigInt::MaxLen=k;BigInt n(s,k);BigInt pre_y=n,y;vector<int> arr;for(int i=0;i<n.size();i++){y=pre_y;int cnt=1;while((y*n).at(i)!=n[i]){y=y*pre_y;cnt+=1;if(cnt ==11)break; //当前位置不存在循环节}if(cnt ==11){cout <<"-1" <<endl;return 0;}arr.push_back(cnt);pre_y=y;}BigInt ans=1;for(int i=0;i<arr.size();i++){ans *= arr[i];}cout << ans << endl;return 0;
}
int main() {acm_1_14_dp_xunhuan_test();return 0;
}
//32 2
//4