【第五讲】 动态规划(2)

简介: 【第五讲】 动态规划(2)

5.4计数类DP

5.4.1 900. 整数划分

一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1。

我们将这样的一种表示称为正整数 n 的一种划分。

现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。


输入格式

共一行,包含一个整数 n。


输出格式

共一行,包含一个整数,表示总划分数量。

由于答案可能很大,输出结果请对 109+7 取模。


数据范围

1≤n≤1000

输入样例:

5

输出样例:

7


#include<bits/stdc++.h>
using namespace std;
const int N=1010,mod=1e9+7;
int f[N];
int main()
{
    int n;
    cin>>n;
    f[0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
        {
            f[j]=(f[j]+f[j-i])%mod;
        }
    }
    cout<<f[n];
    return 0;
}

5.5数位统计DP

5.5.1 338. 计数问题

给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。

例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3 次等等…


输入格式

输入包含多组测试数据。

每组测试数据占一行,包含两个整数 a 和 b。

当读入一行为 0 0 时,表示输入终止,且该行不作处理。


输出格式

每组数据输出一个结果,每个结果占一行。

每个结果包含十个用空格隔开的数字,第一个数字表示 0 出现的次数,第二个数字表示 1 出现的次数,以此类推。


数据范围

0<a,b<100000000

输入样例:

1 10

44 497

346 542

1199 1748

1496 1403

1004 503

1714 190

1317 854

1976 494

1001 1960

0 0

输出样例:

1 2 1 1 1 1 1 1 1 1

85 185 185 185 190 96 96 96 95 93

40 40 40 93 136 82 40 40 40 40

115 666 215 215 214 205 205 154 105 106

16 113 19 20 114 20 20 19 19 16

107 105 100 101 101 197 200 200 200 200

413 1133 503 503 503 502 502 417 402 412

196 512 186 104 87 93 97 97 142 196

398 1375 398 398 405 499 499 495 488 471

294 1256 296 296 296 296 287 286 286 247


#include<bits/stdc++.h>
using namespace std;
int get(vector<int> num,int l,int r)
{
    int res=0;
    for(int i=l;i>=r;i--) res=res*10+num[i];
    return res;
}
int power10(int x)
{
    int res=1;
    while(x--) res*=10;
    return res;
}
int mycount(int n,int x)
{
    if(!n) return 0;
    vector<int> num;
    while(n)
    {
        num.push_back(n%10);
        n/=10;
    }
    n=num.size();
    int res=0;
    for(int i=n-1-!x;i>=0;i--)
    {
        if(i<n-1)
        {
            res+=get(num,n-1,i+1)*power10(i);
            if(!x) res-=power10(i);
        }
        if(num[i]==x) res+=get(num,i-1,0)+1;
        else if(num[i]>x) res+=power10(i);
    }
    return res;
}
int main()
{
    int a,b;
    while(cin>>a>>b)
    {
        if(a==0&&b==0)
            break;
        if(a>b) swap(a,b);
        for(int i=0;i<10;i++)
            cout<<mycount(b,i)-mycount(a-1,i)<<" ";
        cout<<endl;
    }
    return 0;
}

5.6状态压缩DP

5.6.1 291. 蒙德里安的梦想

求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

如下图所示:


输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。


输出格式

每个测试用例输出一个结果,每个结果占一行。

数据范围

1≤N,M≤11

输入样例:

1 2

1 3

1 4

2 2

2 3

2 4

2 11

4 11

0 0

输出样例:

1

0

1

2

3

5

144

51205


#include<bits/stdc++.h>
using namespace std;
const int N=12,M=1<<N;
int n,m;
long long f[N][M];
bool st[M];
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        if(n==0&&m==0)
            break;
        for(int i=0;i<1<<n;i++)
        {
            int cnt=0;
            st[i]=true;
            for(int j=0;j<n;j++)
            {
                if(i>>j&1)
                {
                    if(cnt&1) st[i]=false;
                    cnt=0;
                }
                else cnt++;
            }
            if(cnt&1) st[i]=false;
        }
        fill(f[0],f[0]+N*M,0);
        f[0][0]=1;
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<1<<n;j++)
            {
                for(int k=0;k<1<<n;k++)
                {
                    if((j&k)==0&&st[j|k])
                        f[i][j]+=f[i-1][k];
                }
            }
        }
        cout<<f[m][0]<<endl;
    }
    return 0;
}

5.6.2 91. 最短Hamilton路径

给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。


输入格式

第一行输入整数 n。

接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。

对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。


输出格式

输出一个整数,表示最短 Hamilton 路径的长度。


数据范围

1≤n≤20

0≤a[i,j]≤107

输入样例:

5

0 2 4 5 1

2 0 6 5 3

4 6 0 8 3

5 5 8 0 5

1 3 3 5 0

输出样例:

18


#include<bits/stdc++.h>
using namespace std;
const int N=20,M=1<<20,INF=1e9;
int n;
int f[M][N],weight[N][N];
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            cin>>weight[i][j];
        }
    }
    fill(f[0],f[0]+M*N,INF);
    f[1][0]=0;
    for(int i=0;i<1<<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(i>>j&&1)
            {
                for(int k=0;k<n;k++)
                {
                    if(i-(1<<j)>>k&1)
                    {
                        f[i][j]=min(f[i][j],f[i-(1<<j)][k]+weight[k][j]);
                    }
                }
            }
        }
    }
    cout<<f[(1<<n)-1][n-1];
    return 0;
}

5.7树形DP

5.7.1 285. 没有上司的舞会

Ural 大学有 N 名职员,编号为 1∼N。

他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。

每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。

现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。

在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。


输入格式

第一行一个整数 N。

接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。

接下来 N−1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。


输出格式

输出最大的快乐指数。


数据范围

1≤N≤6000,

−128≤Hi≤127

输入样例:

7

1

1

1

1

1

1

1

1 3

2 3

6 4

7 4

4 5

3 5

输出样例:

5


#include<bits/stdc++.h>
using namespace std;
const int N=6010;
int n;
int happy[N];
int h[N],e[N],ne[N],idx;
int f[N][2];
bool has_father[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
    f[u][1]=happy[u];
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        dfs(j);
        f[u][0]+=max(f[j][0],f[j][1]);
        f[u][1]+=f[j][0];
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>happy[i];
    fill(h,h+N,-1);
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        cin>>a>>b;
        add(b,a);
        has_father[a]=true;
    }
    for(int i=1;i<=n;i++)
    {
        if(!has_father[i])
        {
            dfs(i);
            cout<<max(f[i][0],f[i][1]);
            break;
        }
    }
    return 0;
}

5.8记忆化搜索

5.8.1 901. 滑雪

给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。

矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。

一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。

当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。

下面给出一个矩阵作为例子:

1 2 3 4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

在给定矩阵中,一条可行的滑行轨迹为 24−17−2−1。

在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−1,沿途共经过 25 个区域。

现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。


输入格式

第一行包含两个整数 R 和 C。

接下来 R 行,每行包含 C 个整数,表示完整的二维矩阵。


输出格式

输出一个整数,表示可完成的最长滑雪长度。


数据范围

1≤R,C≤300,

0≤矩阵中整数≤10000

输入样例:

5 5

1 2 3 4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

输出样例:

25


#include<bits/stdc++.h>
using namespace std;
const int N=310;
int r,c;
int g[N][N];
int f[N][N];
int X[4]={0,0,1,-1},Y[4]={1,-1,0,0};
int dp(int x,int y)
{
    int &v=f[x][y];
    if(v!=-1) return v;
    v=1;
    for(int i=0;i<4;i++)
    {
        int a=x+X[i],b=y+Y[i];
        if(a>=1&&a<=r&&b>=1&&b<=c&&g[a][b]<g[x][y])
        {
            v=max(v,dp(a,b)+1);
        }
    }
    return v;
}
int main()
{
    cin>>r>>c;
    for(int i=1;i<=r;i++)
        for(int j=1;j<=c;j++)
            cin>>g[i][j];
    fill(f[0],f[0]+N*N,-1);
    int res=0;
    for(int i=1;i<=r;i++)
    {
        for(int j=1;j<=c;j++)
        {
            res=max(res,dp(i,j));
        }
    }
    cout<<res<<endl;
    return 0;
}

相关文章
|
6月前
|
C++ NoSQL 容器
动态规划二
动态规划二
|
6月前
|
C++ NoSQL 容器
动态规划三
动态规划三
|
算法
【学会动态规划】按摩师(11)
【学会动态规划】按摩师(11)
53 0
|
6月前
|
机器人 NoSQL 容器
动态规划一
动态规划一
|
6月前
动态规划
动态规划
53 0
|
机器学习/深度学习 算法
动态规划详解
前段时间一直在做关于数据结构的题,也算是对数据结构有了一定的了解,知道了有些数据结构的基本算法。现在刚刚开始接触动态规划,其实写这篇文章的初衷是一来锻炼一下自己的总结能力,二来也是希望通过这篇文章,来指引和我一样的初学者,废话不多说了,开始吧。
56 0
|
定位技术
动态规划题:夺宝奇兵
动态规划题:夺宝奇兵
88 0
动态规划-子序列问题
前言 在上篇文章我们学习了动态规划的公共子序列问题,现在我们来学习下动态规划的单字符串子序列问题。
|
算法 前端开发 JavaScript
理解动态规划
理解动态规划
理解动态规划