zoukankan      html  css  js  c++  java
  • 7.29 dp动态规划

    A题:

    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


    很好列出状态转移方程 注意边界情况
    #include <bits/stdc++.h>
    using namespace std;
    int a[1010][1010];
    long long dp[1010][1010];
    int main()
    {
       int t;
       cin>>t;
       while(t--)
       {
           int n,m;
           scanf("%d%d",&n,&m);
           int i,j;
           for(i=1;i<=n;i++)
           {
               for(j=1;j<=m;j++)
               {
                   scanf("%d",&a[i][j]);
               }
           }
           for(i=1;i<=n;i++)
           {
               for(j=1;j<=m;j++)
               {
                   if(i==1&&j==1)dp[i][j]=a[i][j];
                   else if(i==1)dp[i][j]=a[i][j]+dp[i][j-1];
                   else if(j==1)dp[i][j]=a[i][j]+dp[i-1][j];
                   else dp[i][j]=a[i][j]+max(dp[i-1][j],dp[i][j-1]);
               }
           }
           printf("%lld
    ",dp[n][m]);
       }
        return 0;
    }
    View Code

    B题:

    Description

    averyboy又遇到麻烦了。他的老板给了他一个问题,如果他不能解决,老板将会开除他。老板给的问题如下,给你一个序列a[1]~a[N]和一个数M你需要从这N个数选出M个数组成一个严格递增的序列,问你一共有多少种选法?averyboy不想被开除,你能帮助他吗?

    Input

    输入的第一行为一个整数T(T <= 100)代表测试数据的组数
    接下来T组测试数据
    每组测试数据第一行为两个整数N, M(1 <= N <= 1000, 1 <= M <= N)其含义如题目
    接下来一行N个整数a[i](1 <= a[i] <= 1e9)

    Output

    对于每组测试数据输出一个整数代表从N个数中选出M个数组成严格递增序列的选法。因为答案可能很大,所以最后你需要对1e9 + 7取模后输出

    Sample Input

    2
    3 2
    1 2 3
    3 2
    3 2 1

    Sample Output

    3
    0

    HINT

    第一组测试数据可以选1,2或者,1,3或者2,3一共三种选法

    此题很难,据说是CCPC原题,要用到动态规划,树状数组,离散化

    dp[i][j]=dp[a[k]<a[i]][j-1]  即以a[i]结束的,长度为J的递增子序列个数等于以比a[i]小的元素结尾的,长度为J-1的递增子序列之和)用树状数组可以简化求和

    树状数组

    更新单点值,查询前缀和

    把值当位置传给update进行更新,update(a[i],1)将a[i]位置的元素个数+1(代表a[i]位置放了一个数),所有包含a[i]的区间和全部加1,查询query(a[i]-1)即可查询到 i 之前小于等于a[i]的元素个数

    现在开一个tree[ ][ ],tree[i][j]代表以a[i]结尾长度为j的递增子序列的区间和,query(a[i]-1,d)即可查询长度为d的 以i之前 小于等于a[i]的元素为结束的 递增子序列个数之和

    因为查询到的是小于等于,而题目要求的是严格递增,要把等于删去,所以我们对排序做一些修改,把 i 之前等于a[i]的元素放到 i 的后面

    因为a[i]值很大,开不了那么大的数组,所以离散化用相对大小代表值

    #include <bits/stdc++.h>
    using namespace std;
    #define LL long long
    const int maxn=1010;
    const int mod=1e9+7;
    LL tree[maxn][maxn];//树状数组,代表区间和
    LL dp[maxn][maxn];//dp[i][j]表示考虑到第i个数,且以a[i]结尾,长度为j的递增序列个数
    int a[maxn];
    int n,m;
    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;//按值从小到大排序
            //例如 5 5 8 ,算第二个5的时候就不会把第一个5算进去,算第一个5的时候后面那个5还没考虑进来呢
        }
    }Node[maxn];
    int Rank[maxn];//离散化后表示相对位置大小的数组(第一小,第二小...)把值的大小转化为相对位置的大小
    int lowbit(int x)
    {
        return x&(-x);
    }
    void update(int loc,int d,int value)
    {
        for(int i=loc;i<=n;i+=lowbit(i))
        {
            tree[i][d]=(tree[i][d]+value)%mod;//更新操作可以这样理解 将loc位置上的值加上了value,则包含这个位置的所有区间和tree[i]都要加上value
        }                                                  //关于哪个区间含有这个元素有详细的证明
    }
    LL query(int loc,int d)
    {
        LL ans=0;
        for(int i=loc;i>=1;i-=lowbit(i))//查询操作是查询从1到loc的前缀区间和
        {
            ans=(tree[i][d]+ans)%mod;
        }
        return ans;
    }
    int main()
    {
        int t;
        cin>>t;
        while(t--)
        {
            scanf("%d%d",&n,&m);
        int i,j;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&Node[i].value);
            Node[i].id=i;
        }
        sort(Node+1,Node+n+1);//将元素按从小到大排好序
        for(i=1;i<=n;i++)//离散化
        {
            Rank[Node[i].id]=i;   //Node已经排好序了,i即代表Node的相对位置大小
        } //例如,第一个元素,它在未排序前的编号为Node[1].id,输入编号,即可知道它现在的值(相对位置大小,可代表值)
        for(i=1;i<=n;i++)  //以a[i]->Rank[i]为终点。考虑到第i个数
        {
            dp[i][1]=1;//终点为i,长度为1的肯定只有它本身这一个啊
            update(Rank[i],1,1);//把以Rank[i]结束,长度为1且个数为1的子序列更新进去
            for(j=2;j<=min(m,i);j++)//从长度为2的开始遍历,考虑i之前的数,长度肯定小于i,并且只考虑到长度为m就够了
            {
                LL temp=query(Rank[i]-1,j-1);//树状数组求出以比Rank[i]小的数结尾,长度为j-1的递增序列有多少个
                dp[i][j]=temp;
                update(Rank[i],j,dp[i][j]);    //把以Rank[i]结尾,长度为j,个数为dp[i][j]的更新进去
            }
        }
        LL ans=0;
        for(i=1;i<=n;i++)
        {
            ans=(ans+dp[i][m])%mod;
        }
        printf("%lld
    ",ans);
        memset(dp,0,sizeof dp);
        memset(tree,0,sizeof tree);
        }
    return 0;
    }
    View Code

    C题:

    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最大

    此题两种解法,可以用线段树,也可以dp

    线段树求区间最大连续和

    #include <bits/stdc++.h>
    #include<algorithm>
    using namespace std;
    const int maxn=1e5+10;
    int a[maxn];
    struct node
    {
        int L,R;
        long long ms,rs,ls,s;
    }Node[maxn<<2];
    void pushup(int i)
    {
       Node[i].ms=max(Node[i<<1].ms,Node[(i<<1)|1].ms);
       Node[i].ms=max(Node[i].ms,Node[i<<1].rs+Node[(i<<1)|1].ls);
       Node[i].ls=max(Node[i<<1].ls,Node[i<<1].s+Node[(i<<1)|1].ls);
       Node[i].rs=max(Node[(i<<1)|1].rs,Node[i<<1].rs+Node[(i<<1)|1].s);
       Node[i].s=Node[i<<1].s+Node[(i<<1)|1].s;
       return;
    }
    void build(int i,int l,int r)
    {
        Node[i].L=l;
        Node[i].R=r;
        if(r==l)
        {
            Node[i].s=a[l];
            Node[i].ms=a[l];
            Node[i].ls=a[l];
            Node[i].rs=a[l];
            return;
        }
        int mid=(l+r)>>1;
        build(i<<1,l,mid);
        build((i<<1)|1,mid+1,r);
        pushup(i);
    }
    long long queryR(int i,int l,int r)
    {
        if(Node[i].L==l&&Node[i].R==r)
        {
            return Node[i].rs;
        }
        int mid=(Node[i].L+Node[i].R)>>1;
        if(r<=mid) return queryR(i<<1,l,r);
        else if(l>mid) return queryR((i<<1)|1,l,r);
        else {
                long long lans=queryR(i<<l,l,mid);
        long long rans=queryR((i<<1)|1,mid+1,r);
        return max(rans,lans+Node[(i<<1)|1].s);
        }
    }
    long long queryL(int i,int l,int r)
    {
        if(Node[i].L==l&&Node[i].R==r)
        {
            return Node[i].ls;
        }
        int mid=(Node[i].L+Node[i].R)>>1;
        if(r<=mid) return queryL(i<<1,l,r);
        else if(l>mid) return queryL((i<<1)|1,l,r);
        else{
                long long lans=queryL(i<<l,l,mid);
        long long rans=queryL((i<<1)|1,mid+1,r);
        return max(lans,rans+Node[i<<1].s);
        }
    }
    long long query(int i,int l,int r)
    {
        if(Node[i].L==l&&Node[i].R==r)
        {
            return Node[i].ms;
        }
        int mid=(Node[i].L+Node[i].R)>>1;
        if(r<=mid) return query(i<<1,l,r);
        else if(l>mid) return query((i<<1)|1,l,r);
        else
        {
            long long  lans=query(i<<1,l,mid);
        long long rans=query((i<<1)|1,mid+1,r);
        long long ans=max(lans,rans);
        return max(ans,queryR(i<<1,l,mid)+queryL((i<<1)|1,mid+1,r));
        }
    }
    int main()
    {
        int t;
        cin>>t;
        while(t--)
        {
            int n;
            scanf("%d",&n);
            int i;
            for(i=1;i<=n;i++)
            {
                scanf("%d",&a[i]);
            }
            build(1,1,n);
            long long ans=query(1,1,n);
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    dp

    #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
    ", ans);
        }
        return 0;
    }
    View Code

    D题:

    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,用前向星存图即可

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;//注意这里的maxn应该开题目给的双倍,因为边是双向的,加边还要开更多
    int a[maxn];
    int dp[maxn];
    bool visit[maxn];
    int n;
    int head[maxn];  //head[i]表示以i为起点的最后一条边的编号;
    struct edge
    {
        int to;//这条边的终点
        int last;  //与自己起点相同的上一条边的编号
    }Edge[maxn*2];//边数组
    int cnt; //记录当前边的编号
    void add(int u,int v)//加边    //起点u,终点v;;
    {
        Edge[cnt].to=v;
        Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来,
        head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边
    }
    void dfs(int root)//这棵树的根节点
    {
        if(a[root]&1)dp[root]=1;//根节点自己
        else dp[root]=0;
        visit[root]=true;  //节点已访问过
        for(int i=head[root];i!=-1;i=Edge[i].last)//把节点当起点,
        {
            int v=Edge[i].to;//子节点是终点
            if(!visit[v])
            {
                dfs(v);//把子节点当根节点去找它的子节点
                dp[root]+=dp[v];//根节点的子节点数加上它的子节点的子节点数
            }
        }
    }
    int main()
    {
        int t;
        cin>>t;
        while(t--)
        {
            scanf("%d",&n);
            int i;
            for(i=1;i<=n;i++)scanf("%d",&a[i]);
            for(i=1;i<=n;i++)head[i]=-1;
            cnt=1;
            for(i=1;i<=n-1;i++)
            {
                int u,v;
                scanf("%d%d",&u,&v);
                add(u,v);
                add(v,u);
            }
            memset(visit,false,sizeof visit);//记得一定要memset
            dfs(1);
            for(i=1;i<=n;i++)printf("%d ",dp[i]);
            printf("
    ");
        }
        return 0;
    }
    View Code


  • 相关阅读:
    WPF一步一脚印系列(1):万事起头难
    php设置时区
    关于我的几个博客
    php如何实现页面跳转
    穷人与富人的区别
    如何抓取关键字在百度搜索的排名
    我的博客园开通了
    在Foxmail中出现SSL连接错误应该如何解决
    javascript实现键盘按下回车时触发
    关于网站分页
  • 原文地址:https://www.cnblogs.com/raincle/p/9387736.html
Copyright © 2011-2022 走看看