动态规划_1

概述

今天集训学的是动态规划,,,也就是dp,,,这玩意早就听说过了,,,一直感觉很难,,,听名字就有些高大上,,,今天了解了其大致的思想,,,四道题也就做了两道,,,还是学长上午讲过的,,自己根据模板直接套的,,,中间那两题完全不知道从哪下手,,,或者说不知道如何实现脑子里的想法,,,,其中B题是cpcc的一道原题,,,用了动态规划,树状数组,离散化三个主要的算法,,,综合性很强,,,难啊啊啊啊,,,

基本

动态规划介绍

(直接粘大佬介绍。。。](https://blog.csdn.net/cc_again/article/details/25866971)

动态规划(英语:Dynamic programming,DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。

动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

简单来说,,动态规划就是一种用于求解包含 重叠子问题 的最优解问题的思想,,,
也就是,将原问题分解为相似的子问题,,在求解的过程中通过子问题求出原问题的解,,,

动态规划满足的性质

一样,,,粘大佬表达,,,

  • 最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

  • 子问题重叠性质 :子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

  • 无后效性 :将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

动态规划主要使用步骤

  • 分析问题 :看满不满足使用动态规划的基本条件,,简单地说就是一个状态不受前面的决策的影响,,这个状态的决策也不会影响后面的状态,,
  • 定义状态 :这一步很重要,,关系到算法的复杂度和 状态转移方程
  • 找出状态转移方程和初始状态,边界状态,利用状态转移方程计算出所有状态
  • 利用上面求解的状态求解问题

套路归套路,,最终还是要看题目的要求,,题意来解,,,

动态规划的复杂度

一般来说复杂度取决于两个方面:

  • 状态本身:一般来说要计算出所有状态,,所以复杂度和定义的状态有关,,比如dp[i][j]类型的复杂度为O(n * m)
  • 状态转移方程:因为一个状态必定从某些子状态转移而来,,所以复杂度还取决与状态转移的复杂度,,有时要在这里选择合适的数据结构来优化,,,比如下面的B题,,,同时这也就引出了各种各样的动态规划的题型,,,如树形dp,,,斜率dp,,,区间dp,,,概率dp,,,等等,,,

练习

Problem A: 你又没有好好听课3

Time Limit: 2 Sec Memory Limit: 128 MB

Description

为了检验你上午有没有好好听课,于是又了这一题。给你一个N*M的方格网,左上角为(1,1)右下角为(N, M),每个方格中有一个数a[i][j],刚开始你在位置(1, 1)你每次可以往下走或者往右走一步,你需要确定一种走的方案,最后走到(N, M),使得途径格子的数的和最大。

Input

输入的第一行一个整数T(T<= 5)代表测试数据的组数

接下里T组测试数据

每组测试数据第一行为两个整数N, M(1 <= N, M <= 1000)代表方格网的大小

接下来N行,每一行M个数,代表a[i][j](1 <= a[i][j] <= 1000)

Output

对于每组测试数据,输出一个整数代表从(1, 1)走到 (N, M)途径的格子的最大的和。

Sample Input

1
2 2
100 1
50 1

Sample Output
151

简单的dp,,,根据题意写出状态转移方程 \(d[i][j] = max(d[i - 1][j] , d[i][j - 1]) + a[i][j];\)

我的代码:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int a[N][N];
int d[N][N];
int n , m;
int dp(int n , int m)
{
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            {
                d[i][j] = max(d[i - 1][j] , d[i][j - 1]) + a[i][j];
                //好像少了特判,,,不过数据过了,,,逃,,,
            }
    return d[n][m];
}

int main()
{
    int t;cin >> t;
    while (t--)
    {
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> a[i][j];
        memset(d , 0 , sizeof(d));
        cout << dp(n , m) << endl;
    }
    return 0;
}

学长的代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 10;
int N, M;
int A[maxn][maxn];
int dp[maxn][maxn];
int main()
{
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &N, &M);
        for(int i = 1; i <= N; i++)
        {
            for(int j = 1; j <= M; j++)
            {
                scanf("%d", &A[i][j]);
            }
        }
        memset(dp, 0, sizeof(dp));
        dp[1][1] = A[1][1];
        for(int i = 1; i <= N; i++)
        {
            for(int j = 1; j <= M; j++)
            {
                if(i == 1 && j == 1) dp[i][j] = A[i][j];        //就是这里的特判,,,,,,,,,,
                else if(i == 1 && j != 1) dp[i][j] = dp[i][j - 1] + A[i][j];
                else if(j == 1 && i != 1) dp[i][j] = dp[i - 1][j] + A[i][j];
                else
                {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + A[i][j];
                }
            }
        }
        printf("%d\n", dp[N][M]);
    }
    return 0;
}

Problem B: averyboy的麻烦

这道题还是真麻烦,,,到现在状态转移方程的实现那里还是有些不懂,,,,,,,噗

主要的推导在代码里,,,还有那两个博客,,,,

我的代码:

#include <iostream>
#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
using namespace std;
const int N = 1e3 + 3;
const int MOD = 1e9 + 7;
typedef long long ll;
int a[N];
int b[N];
int n , m;
ll dp[N][N];
void update(int loc , int x , int val)  //更新
{
    for (int i = loc; i <= n; i+=lowbit(i))
    {
        dp[i][x] = (dp[i][x] + val) % MOD;
    }
}
int query (int loc , int x)             //求和
{
    int ans = 0;
    for (int i = loc; i >= 1; i -= lowbit(i))
        ans = (dp[i][x] + ans) % MOD;
    return ans;
}
int main()
{
    ios_base::sync_with_stdio(0);
    //freopen("data.in" , "r" , stdin);
    int t;cin >> t;
    while (t--)
    {
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
            b[i] = a[i];
        }
        //离散化,,,,
        sort(b + 1,b + 1 + n);
        for (int i = 1; i<= n; i++)
        {
            a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;  //a[i]存储的是该位置是第几大的元素
        }

        memset(dp , 0 , sizeof(dp));
        //动态规划,状态转移方程dp[i][j] = sum(dp[k][j-1]) k = {1 , i - 1}
        for (int i = 1; i <= n; i++)
        {

            for (int j = 1; j <= min(i , m); j++)
            {
                if (j == 1) update(a[i] , 1 , 1);
                else
                {
                    ll tmp = query(a[i] - 1 , j - 1);
                    update(a[i] , j , tmp);
                }
            }
        }
        ll ans = query(n , m);

        cout << ans << endl;
    }
    return 0;
}
//https://blog.csdn.net/snowy_smile/article/details/49565493
//https://blog.csdn.net/loy_184548/article/details/50073559

学长的代码:

有空在研究,,,QAQ

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 10;
typedef long long LL;
const LL mod = 1e9 + 7;
int N, M;
int a[maxn];
LL Tree[maxn][maxn];
LL dp[maxn][maxn];//dp[i][j]表示考虑到第i个数,且以第a[i]个数结尾,长度为j的递增序列个数
struct node{
    int value;
    int id;
    bool operator <(const node &res) const{
        if(value == res.value) return id > res.id;
        else return value < res.value;
    }
}Node[maxn];
int Rank[maxn];
void init()
{
    memset(Tree, 0, sizeof(Tree));
    memset(dp, 0, sizeof(dp));
}
int lowbit(int x)
{
    return x&(-x);
}
void add(int loc, int d, LL value)
{
    for(int i = loc; i <= N; i += lowbit(i))
    {
        Tree[i][d] = (Tree[i][d] + value) % mod;
    }
}
LL get(int loc, int d)
{
    LL ans = 0;
    for(int i = loc; i >= 1; i -= lowbit(i))
    {
        ans = (ans + Tree[i][d]) % mod;
    }
    return ans;
}
int main()
{
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &N, &M);
        init();
        for(int i = 1; i <= N; i++)
        {
            scanf("%d", &Node[i].value);
            Node[i].id = i;
        }
        sort(Node + 1, Node + N + 1);
        for(int i = 1; i <= N; i++)
        {
            Rank[Node[i].id] = i;
        }
        for(int i = 1; i <= N; i++)
        {
            dp[i][1] = 1;
            add(Rank[i], 1, 1);
            for(int j = 2; j <= min(M, i); j++)
            {
                LL temp = get(Rank[i] - 1, j - 1);
                dp[i][j] = (dp[i][j] + temp) % mod;
                add(Rank[i], j, dp[i][j]);
            }
        }
        LL ans = 0;
        for(int i = 1; i <= N; i++)
        {
            ans = (ans + dp[i][M]) % mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

Problem C: averyboy的区间2

Time Limit: 2 Sec Memory Limit: 128 MB

Description

不仅天外天喜欢子区间,averyboy也非常喜欢子区间。现在天外天给averyboy一个长度为N的序列a[1]~a[N],天外天让averyboy找出一个子区间[l, r]使得这个子区间数的和要比其他子区间数的和要大

Input

第一行一个整数T(T <= 10)代表测试数据的组数

接下来T组测试数据

每组测试数据第一行为一个整数N(1 <= N <= 1e5)代表序列的长度

接下来一行N个整数a[i](-1000 <= a[i] <= 1000)代表序列a[i]

Output

对于每组测试数据,输出一个整数,代表最大的子区间和。

Sample Input
2
3
1 -100 3
4
99 -100 98 2

Sample Output
3
100

HINT

第一组测试样例,选择区间[3,3]和为3最大,第二组测试样例选择区间[3, 4]和为98 + 2 = 100最大

主要是状态转移方程写出来就行了,,,,QAQ

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 5;
int a[N];
int n;
int dp;
int DP()
{
    int m = -INF;
    int dp = 0;
    for (int i = 1; i <= n; i++)
    {
        dp = max (dp + a[i] , a[i]);
        m = max (m , dp);
    }
    return m;
}
 
 
int main()
{
    int t;cin >> t;
    while (t--)
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
        }
 
        cout << DP() << endl;
    }
    return 0;
}

学长的代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 10;
int N;
int a[maxn];
int dp[maxn];
int main()
{
    //freopen("data.in", "r", stdin);
   // freopen("data.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &N);
        for(int i = 1; i <= N; i++)
        {
            scanf("%d", &a[i]);
        }
        int ans = -1000000001;
        int acc = 0;
        for(int i = 1; i <= N; i++)
        {
            dp[i] = a[i] + acc;
            if(acc + a[i] > 0) acc += a[i];
            else
            {
                acc = 0;
            }
        }
        for(int i = 1; i <= N; i++)
        {
            if(dp[i] > ans)
            {
                ans = dp[i];
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

Problem D: averyboy的苹果树

Time Limit: 2 Sec Memory Limit: 128 MB

Description

averyboy家有一棵苹果树。把这棵苹果树看成一个由N(编号为1~N)个节点组成的以1号节点为根的有根树。每个节点上有一个苹果,每个苹果也有一个营养价值a[i]。现在averyboy想知道以每个节点为根的子树上营养价值为奇数的节点的个数。

Input

输入第一行为一个整数T(T <= 5)代表测试数据的组数

接下来T组测试数据

每组测试数据第一行为一个整数N(1 <= N <= 1e5)
接下来一行N个非负整数a[i]代表每一个节点上的一个苹果的营养价值(0 <= a[i] <= 1e6)
接下来N - 1行,每一行两个整数u, v代表u, v之间有一条边(1 <= u, v <= N)

Output

对于每组测试数据,输出一行N个数,第i个数代表以第i节点为根的子树(子树包括自己)上苹果营养价值为奇数的个数

Sample Input
2
3
1 2 3
1 2
2 3
3
1 1 1
1 2
2 3

Sample Output
2 1 1
3 2 1

HINT

在第一组样例中,以1为根的子树包括节点1,2,3但是由于2号节点上的苹果营养价值为2不是奇数,所以以1为根的子树上一共有2个营养价值为奇数的苹果。以2为根的子树包括节点2, 3,所以只有1个营养价值为奇数的苹果.以3为根的子树就是3自身,所以也只有1个营养价值为奇数的苹果。所以最后输出2 1 1

上课时学长讲过,,,所以直接套模板了,,,dp是在每一个节点,,dp[i] = sum(dp[son]) + 1;

我的代码:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
const int maxm = 1e5 * 3;
int head[maxn];
int a[maxn];
int cnt;
int n , m;

struct edge
{
    int to;
    int w;
    int last;
}Edge[maxm];
void add(int u , int v , int w)
{
    Edge[cnt].to = v;
    Edge[cnt].w = w;
    Edge[cnt].last = head[u];
    head[u] = cnt++;
}
void add(int u , int v)
{
    Edge[cnt].to = v;
    //Edge[cnt].w = w;
    Edge[cnt].last = head[u];
    head[u] = cnt++;
}
bool vis[maxn];
int dp[maxn];
void tree_dfs(int rt)
{
    //dp[rt] = 1;
    vis[rt] = true;
    for (int i = head[rt]; i != -1; i = Edge[i].last)
    {
        int v = Edge[i].to;
        if (!vis[v])
        {
            tree_dfs(v);
            dp[rt] += dp[v];
        }
    }
}
void init()
{
    memset(head , -1 , sizeof(head));
    memset(vis , false , sizeof(vis));
    memset(dp , 0 , sizeof(dp));
    cnt = 0;
}
int main()
{
    //ios_base::sync_with_stdio(0);
    int t;
    //cin >> t;
    //freopen("data.in" , "r" , stdin);
    scanf("%d" , &t);
    while (t--)
    {
        init();
        scanf("%d" , &n);
        //cin >> n;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d" , &a[i]);
            //cin >> tmp;
        }

        for (int i = 1; i <= n; i++)
            if (a[i] & 1)
                dp[i] = 1;

        int u , v;
        for (int i = 1; i <= n - 1; i++)
        {
            scanf("%d%d" , &u , &v);
            //cin >> u >> v;
            add(u , v);
            add(v , u);
        }

        tree_dfs(1);

//        for (int i = 1; i <= n; i++)
//            cout << dp[i] << " ";
//        cout << endl;
        for (int i = 1; i <= n; i++)
            printf("%d " , dp[i]);
        printf("\n");

    }
    return 0;
}

记得数组开大。,,,,,

学长的代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 10;
vector<int> g[maxn];
int N;
int a[maxn];
int dp[maxn];
bool visit[maxn];
void init()
{
    for(int i = 1; i <= N; i++)
    {
        g[i].clear();
    }
    memset(dp, 0, sizeof(dp));
    memset(visit, false, sizeof(visit));
}
void dfs(int root)
{
    if(a[root]&1) dp[root] = 1;
    visit[root] = true;
    int len = g[root].size();
    for(int i = 0; i < len; i++)
    {
        int v = g[root][i];
        if(!visit[v])
        {
            dfs(v);
            dp[root] += dp[v];
        }
    }
}
int main()
{
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &N);
        for(int i = 1; i <= N; i++)
        {
            scanf("%d", &a[i]);
        }
        init();
        int u, v;
        for(int i = 1; i < N; i++)
        {
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        dfs(1);
        for(int i = 1; i <= N; i++)
        {
            if(i != N) printf("%d ", dp[i]);
            else printf("%d\n", dp[i]);
        }
    }
    return 0;
}

其他

动态规划是大坑,,,,得之后好好多做题,,,

挖个坑:
https://blog.csdn.net/cc_again/article/details/25866971#commentBox

posted @ 2018-07-29 23:27  31415926535x  阅读(308)  评论(0编辑  收藏  举报