动态规划总结(暂时非常简略

动态规划

首要思路:分为状态表示状态计算,而状态表示需要考虑到集合属性

 1. 数字三角形模型:基于矩阵,有上下行的联系。
【方格取数】
从左上角走到右下角再返回,取过的数字变成0,求两趟数字之和最大。
f[2*N][N][N] 表示 第 k 步 横坐标走到 i1  另一个横坐标走到 i2 的最大价值
#include<bits/stdc++.h>
using namespace std;
const int N = 16;
int f[2*N][N][N]; // 按照步数 优化为 三维 
int a[N][N];
int main()
{
    int n; cin >> n;
    int x,y,z;
    while( cin >> x >> y >> z, x||y||z) a[x][y] = z;
    for(int k = 2; k <= 2*n; k ++ )
    {
        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)
                {
                    int &x = f[k][i1][i2];
                    int t = a[i1][j1];
                    if(i1!=i2) t += a[i2][j2];
                    x = max(x,f[k-1][i1-1][i2-1]+t);
                    x = max(x,f[k-1][i1-1][i2]  +t);
                    x = max(x,f[k-1][i1][i2-1]  +t);
                    x = max(x,f[k-1][i1][i2]    +t);
                }
            }
        }
    }
    cout<<f[2*n][n][n]<<endl;
    return 0;
}
2.  最长上升子序列模型:有序的数列(升或降)
【最长公共上升子序列】
f[i][j] 表示 a[0~i] 和 b[0~j] 中以 b[j] 结尾的最长公共上升子序列的长度
#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &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]);
    printf("%d\n", res);

    return 0;
}

3.  背包模型:
 选与不选,基本状态表示:所有只从前 i 个物品中选,总体积不超过 j 的集合
 属性有:Max Min Count ...
01背包:
一个物品只能用一次 f[i,j] = max(f[i-1,j],f[i-1,j-v]+w)

完全背包:遍历体积为 顺序 遍历
一个物品可以使用无限次 f[i,j] = max(f[i-1,j],f[i,j-v]+w)

多重背包:(朴素,二进制优化,单调队列优化)
一个物品可以使用 s[i]for 物品
	for 体积
		for 决策
【混合背包】
s ==-1 01背包
s == 0 完全背包
s >  0 多重背包
#include<iostream>
using namespace std;
const int N = 1e3+6;
int f[N];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 0; i < n; i ++ )
    {
        int v, w, s; 
        cin >> v >> w >> s;
        
        if(s == 0) {
            for(int j = v; j <= m; j ++ )
                f[j] = max(f[j], f[j-v]+w);
        }else {
            
            if(s==-1) s = 1;
            
            for(int k = 1; k <= s; k*= 2 )
            {
                for(int j = m; j <= m && k*v <= j; j -- )
                    f[j] = max(f[j], f[j-k*v]+k*w);
                s-=k;
            }
            if(s)
            {
                for(int j = m; j >= v*s; j -- )
                    f[j] = max(f[j], f[j-s*v]+s*w);
            }
            
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

4.  状态机模型:理清每个状态 如何转移 画出图形
【股票买卖V】
f[i][j]
j==0 当前没有股票,且不处于冷冻期 (空仓)
j==1 当前有股票 (持仓)
j==2 当前没有股票,且处于冷冻期 (冷冻期)
#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int n;
int w[N];
int f[N][3];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; ++ i) cin >> w[i];

    memset(f, -0x3f, sizeof f);
    f[0][0] = 0;
    for (int i = 1; i <= n; ++ i)
    {
        f[i][0] = max(f[i - 1][0], f[i - 1][2]);
        f[i][1] = max(f[i - 1][1], f[i - 1][0] - w[i]);
        f[i][2] = f[i - 1][1] + w[i];
    }
    cout << max(f[n][0], f[n][2]) << endl;
    return 0;
}

5.  状态压缩DP: 用二进制来对数据进行压缩 用01来表示状态 
 check() 函数和 for 循环 很重要
【小国王】
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,
求使它们无法互相攻击的方案总数。
f[i][k][j] 表示 到 第 i 行 已经摆放了 k 个国王 且状态为 j 的 方案数

#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

typedef long long LL;

const int N = 12, M = 1 << 10, K = 106;

int n,m;
vector<int> state; // 满足要求的行的情况
int cnt[M];
vector<int> head[M];
LL f[N][K][M];

bool check(int state)
{
    for(int i = 0; i < n; i ++ )
        if((state >> i & 1) && (state >> (i+1) & 1))
            return false;
    return true;
}

int count(int state)
{
    int res = 0;
    for(int i = 0; i < n; i ++ )
        res += state >> i & 1;
    return res;
}

int main()
{
    cin >> n >> m;

    for(int i = 0; i < 1 << n; i ++ )
        if(check(i))
        {
            state.push_back(i);
            cnt[i] = count(i);
        }

    for(int i = 0; i < state.size(); i ++ )
        for(int j = 0; j < state.size(); j ++ )
        {
            int a = state[i], b = state[j];
            if((a&b) == 0 && check(a|b))
                head[i].push_back(j);
        }

    f[0][0][0] = 1;
    for(int i = 1; i <= n+1; i ++ )
        for(int j = 0; j <= m; j ++ )
            for(int a = 0; a < state.size(); a ++ )
                for(int b : head[a])
                {
                    int c = cnt[state[a]];
                    if(j >= c)
                        f[i][j][a] += f[i-1][j-c][b];
                }
    cout<<f[n+1][m][0]<<endl;

    return 0;
}

 6. 区间DP:考虑一段区间的问题 对 len 进行最外层循环
【环形石子合并】
将环形 转换为 2倍线性 
将累加看作前缀和 求前缀和 的 和 即为值
关键 ==> f[l][r] = min(f[l][r], f[l][k]+f[k+1][r]+s[r]-s[l-1]);

#include<bits/stdc++.h>
using namespace std;
const int N = 406;
int n;
int 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 l = 1; l + len -1 <= 2*n; l ++ )
        {
            int r = l + len - 1;
            if( l == r) f[l][r] = g[l][r] = 0;
            else
            {
                for(int k = l; k < r; k ++ )
                {
                    f[l][r] = min(f[l][r], f[l][k]+f[k+1][r]+s[r]-s[l-1]);
                    g[l][r] = max(g[l][r], g[l][k]+g[k+1][r]+s[r]-s[l-1]);
                }
            }
        }
    }
    int ans1 = 0x3f3f3f3f, ans2 = -0x3f3f3f3f;
    for(int i = 1; i <= n; i ++ )
    {
        ans1 = min(ans1,f[i][i+n-1]);
        ans2 = max(ans2,g[i][i+n-1]);
    }
    cout<<ans1<<endl<<ans2<<endl;
    return 0;
}
 7. 树形DP: 以树的数据结构 来 构成问题 
 其实就是 换一种数据的存储方式 本质上 跟 线性也无差别
【二叉苹果树】 
求 剩下 m 个树枝 收获苹果的最大价值
相当于 求 m 条边的一个连通块且1为根节点 的最大价值
f[i][j] 表示 以 i 为根节点 剩余 j 条边的最大价值
#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, M = N << 1;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int f[N][N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int father)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int ver = e[i];
        if (ver == father) continue;
        dfs(ver, u);
        for (int j = m; j >= 0; j -- )
            for (int k = 0; k <= j - 1; k ++ )  //枚举体积预留一条连向父节点的边
                f[u][j] = max(f[u][j], f[u][j - k - 1] + f[ver][k] + w[i]);
    }
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    dfs(1, -1);
    printf("%d\n", f[1][m]);
    return 0;
}

【树的最长路径】
树的遍历 参考以下 dfs 方式
int dfs(int u,int father)
{
    int dist = 0;
    int d1 = 0, d2 = 0;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == father) continue;
        int d = dfs(j,u) + w[i];
        dist = max(dist, d);
        
        if(d >= d1) d2 = d1, d1 = d;
        else if(d > d2) d2 = d;
    }
    
    ans = max(ans,d1+d2);
    
    return dist;
}
 8. 数位DP:求一个区间内满足条件的数字有多少个
【度的量化】
求 在 [l,r] 范围内 b进制数中含有k个1的数的个数
#include<bits/stdc++.h>
using namespace std;
const int N = 36;
int f[N][N],l,r,k,b;


void init()
{
    for(int i = 0; i < N; i ++ )
        for(int j = 0; j <= i; j ++ )
            if(!j) f[i][j] = 1;
            else f[i][j] = f[i-1][j] + f[i-1][j-1];
            
}

int dp(int n)
{
    int res = 0;
    int last = 0;
    if(!n) return 0;
    
    vector<int> nums;
    while(n) nums.push_back(n%b), n/=b;
    
    for(int i = nums.size()-1; i >= 0; i -- )
    {
        int x = nums[i];
        if(x)
        {
            res += f[i][k-last];
            if(x > 1)
            {
                if(k-last-1 >= 0) res += f[i][k-last-1];
                break;
            }
            else
            {
                last ++;
                if(last > k) break;
            }
        }
        if( !i && last == k) res++;
    }
    return res;
}
int main()
{
    init();
    cin >> l >> r >> k >> b;
    cout<<dp(r) - dp(l-1)<<endl;
    return 0;
}
 9.  单调队列优化DP:
【最大子序列和】
长度为 n 的序列中 m 长度子序列的最大值 
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+6, INF = 0x3f3f3f3f;
int s[N];
int n,m;
int q[N],hh,tt;
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ ) scanf("%d",&s[i]), s[i]+=s[i-1];
    
    int res = -INF;
    for(int i = 1; i <= n; i ++ )
    {
        if(q[hh] < i-m) hh++;
        res = max(res, s[i]-s[q[hh]]);
        while(hh <= tt && s[q[tt]] >= s[i]) tt--;
        q[++tt] = i;
    }
    cout<<res<<endl;
    return 0;
}
10.  斜率优化DP
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值