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


  • 相关阅读:
    hdu6229 Wandering Robots 2017沈阳区域赛M题 思维加map
    hdu6223 Infinite Fraction Path 2017沈阳区域赛G题 bfs加剪枝(好题)
    hdu6438 Buy and Resell 买卖物品 ccpc网络赛 贪心
    hdu6441 Find Integer 求勾股数 费马大定理
    bzoj 1176 Mokia
    luogu 3415 祭坛
    bzoj 1010 玩具装箱
    bzoj 3312 No Change
    luogu 3383【模板】线性筛素数
    bzoj 1067 降雨量
  • 原文地址:https://www.cnblogs.com/raincle/p/9387736.html
Copyright © 2011-2022 走看看