luogu 【动态规划1】动态规划的引入
P1216 数字三角形
每个节点的值只受左上,右上两节点影响。索引从1开始,避免处理边界问题。
int n,ans,a[1005][1005],dp[1005][1005];
//pull: dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + dp[i][j];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= i; j++) cin >> dp[i][j];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= i;j++) dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + dp[i][j];
}
for(int i = 1; i <= n; i++) ans = max(ans, dp[n][i]);
cout << ans;
return 0;
}
P1434 滑雪
一个二维数组,每个数字代表当前点的高度,可以上下左右移动且只可以由高的点移动到低的点,找出最大移动次数 -- 记忆化搜索。
int n,m,ans,d[110][110],h[110][110],dir[2][4] = {-1,1,0,0,0,0,-1,1};
//d[x][y]记录(x,y)的最大移动次数
int dfs(int x,int y){
if(d[x][y]) return d[x][y];
d[x][y] = 1;//最小下滑次数为1
for(int i = 0; i < 4; i++){
int tx = x + dir[0][i];
int ty = y + dir[1][i];
if(h[tx][ty] > h[x][y]) d[x][y] = max(d[x][y], dfs(tx, ty) + 1);
}
return d[x][y];
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++) cin >> h[i][j];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++) ans = max(ans, dfs(i,j));
}
cout << ans;
return 0;
}
P2196 挖地雷
n个地窖,每个地窖中有若干个地雷,地窖间的连接情况已给出,设计一个挖出地雷最多的方案。
- n <= 20,可以直接暴力...
- 思路同最长上升子序列,dp[i]表示以i为终点时挖到的最大地雷数
- 枚举i之前的所有的地窖,与i连接时,判断是否可以通过这个地窖挖取更多的地雷,pre数组记录各节点的前驱,更新dp数组的同时,更新相应的前驱。
int n,m,ans,t,idx,dp[110],link[25][25],w[25],pre[25];
void print(int i){
if(pre[i]) print(pre[i]);
cout << i << ' ';
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> w[i];
for(int i = 1; i <= n - 1; i++){
for(int j = i + 1; j <= n; j++) cin >> link[i][j];
}
dp[1] = w[1];
for(int i = 2; i <= n; i++){
dp[i] = w[i];
for(int j = 1; j < i; j++){
if(link[j][i] && dp[i] < dp[j] + w[i]){
dp[i] = dp[j] + w[i];
pre[i] = j;
}
}
if(dp[i] > ans){
ans = dp[i];
t = i;
}
}
print(t);
cout << endl << ans;
return 0;
}
P4017 最大食物链计数
n类生物,m种关系,求最大食物链数量mod 80112002的值。n个点组成了有m条边的DAG --> 入度为0的点到出度为0的点之间路径的个数 --> 可以用拓扑排序 or 记忆化搜索
- 拓扑排序:
- num数组记录各节点权值,初始入度为0的点权值为1,删除这些节点时,将权值加到所有相邻的节点上。
- 排序结束后,统计出度为0的节点的权值和
int n,m,a,b,idx,ans,in[5005],out[5005],h[5005],num[5005];//in out 记录个点出度 入度
const int mod = 80112002, kN = 5e5 + 5;
struct node{
int e,ne;
}edges[kN];
void add(int a, int b){//s -> e
edges[idx].e = b;
edges[idx].ne = h[a];
h[a] = idx++;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
memset(h, -1, sizeof h);
cin >> n >> m;
for(int i = 0; i < m; i++){
cin >> a >> b;
add(a, b);
++out[a];
++in[b];
}
queue<int> q;
for(int i = 1; i <= n; i++){
if(in[i] == 0){//入度为0的点入队
num[i] = 1,q.push(i);
}
}
while(!q.empty()){
int top = q.front();
q.pop();
//删除top,相邻点入度-1
for(int i = h[top]; i != -1; i = edges[i].ne){
--in[edges[i].e];
num[edges[i].e] = (num[edges[i].e] + num[top]) % mod;
if(in[edges[i].e] == 0) q.push(edges[i].e);
}
}
//搜索出度为0的点,计算答案
for(int i = 1; i <= n; i++){
if(!out[i]) ans = (ans + num[i]) % mod;
}
cout << ans;
return 0;
}
- 记忆化搜索
- 入度为0的节点:起点,出度为0的节点:终点
- 搜索到终点即为一条最长食物链。
- dp[i]表示i到终点有多少条路径,搜索时存在dp[i],返回相应值
- 搜索所有的起点,结果求和取模。
P1048 采药
裸01背包,dp(i,j)表示前i株药材采摘耗时为j的最大价值。由于i状态只与i - 1状态有关,可使用滚动数组优化,dp[i]表示耗时为i时的最大价值.
#include<iostream>
#include<algorithm>
#define endl '\n'
using namespace std;
int n,m,ans,dp[1010],t,w;
//int t[110],w[110];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> m;
for(int i = 0; i < m; i++){
cin >> t >> w;
for(int j = n; j >= t; j--){//大-小枚举,只可使用一次
dp[j] = max(dp[j], dp[j - t] + w);
}
}
cout << dp[n];
/*
for(int i = 1; i <= m; i++) cin >> t[i] >> w[i];
for(int i = 1; i <= m; i++){
for(int j = 0; j <= n; j++){
dp[i][j] = dp[i - 1][j];//第i间物品有选 or 不选两种状态
if(j >= t[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - t[i]] + w[i]);
}
}
cout << dp[m][n];*/
return 0;
}
P1616 疯狂的采药
完全背包问题,滚动数组优化的01背包中时间从小到大枚举即可。
for(int i = 0; i < m; i++){
cin >> t >> w;
for(int j = t; j <= n; j++) dp[j] = max(dp[j], dp[j - t] + w);
}
P1802 五倍经验日
01背包...,每一个好友都有输 or 赢两种状态,dp[j]表示消耗j个药物获得的最大经验
- j >= use,win or lose : dp[j] = max(dp[j] + lose, dp[j - use] + win);
- j < use, lose: dp[j] += lose;
- 存在use == 0的情况
int n,x,lose,win,use;
long long dp[10010];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> x;
for(int i = 1; i <= n; i++){
cin >> lose >> win >> use;
for(int j = x; j >= use; j--) dp[j] = max(dp[j] + lose, dp[j - use] + win);
for(int j = use - 1; j >= 0; j--) dp[j] += lose;
}
cout << dp[x] * 5;
return 0;
}
P1002 过河卒
//A(0,0) --> B(n, m)的路径条数,其中马所控制的八个点不可以走
//dp[x][y]表示到达x,y点的路径数;
//由于卒只能向下向右走:dp[x][y] = dp[x - 1][y] + dp[x][y - 1]
int n,m,hx,hy,dir[4] = {1,-1,2,-2};
long long dp[25][25];
bool book[25][25];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> m >> hx >> hy;
book[hx][hy] = true;
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
if(i == j) continue;
if(abs(dir[i]) == abs(dir[j])) continue;
int tx = hx + dir[i];
int ty = hy + dir[j];
book[tx][ty] = true;
}
}
for(int i = 1; i <= n && !book[i][0]; i++) dp[i][0] = 1;
for(int i = 1; i <= m && !book[0][i]; i++) dp[0][i] = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(!book[i - 1][j]) dp[i][j] +=dp[i - 1][j];
if(!book[i][j - 1]) dp[i][j] += dp[i][j - 1];
}
}
cout << dp[n][m];
return 0;
}
- 滚动数组优化,待填坑...