7.29 dp动态规划

Posted raincle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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原题,要用到动态规划,树状数组,离散化

树状数组求逆序对的思想,把值当位置,可求出第i个元素前面比a[i]小的元素个数,在求和上把复杂度从n降到logn

因为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位置上的值为0元素改成了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]为终点。考虑到第i个数
    {
        dp[i][1]=1;//终点为i,长度为1的肯定只有它本身这一个啊
        update(Rank[i],1,1);//把长度为1且个数为1的更新进去
        for(j=2;j<=min(m,i);j++)//从长度为2的开始遍历,考虑i之前的数,长度肯定小于i,并且只考虑到长度为m就够了
        {
            LL temp=query(Rank[i]-1,j-1);//以刚好比a[i]小的数结尾,长度为j-1的递增序列有多少个
            dp[i][j]=(dp[i][j]+temp)%mod;
            update(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

 





























以上是关于7.29 dp动态规划的主要内容,如果未能解决你的问题,请参考以下文章

动态规划---状压dp

51nod 1021 石子归并 (动态规划 简单代码)

动态规划算法(Dynamic Programming,简称 DP)

B监狱 noip 模拟 7.29(区间DP)

动态规划_计数类dp_数位统计dp_状态压缩dp_树形dp_记忆化搜索

动态规划-数位dp