动态规划

Posted iwillenter-top1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划相关的知识,希望对你有一定的参考价值。

10.Paths through the Hourglass,UVa10564

题意:

有一个沙漏,第一行有(n)个格子,第二行有(n-1)个格子(cdotscdots)最中间的行只有1个格子,然后它下面一行2个格子,再下面一行3个格子(cdotscdots)最后一行(n)个格子,如图(1-60)所示。你可以从第一行开始往下走,每次往下走一行,往左或往右走一列,但不能走出沙漏。你的目标是让沿途经过的所有整数之和恰好为一个给定的整数(S)。求出符合上述条件的路径条数和一条路径。

如果有多条路径,起点编号应尽量小(第一行格子从左到右编号为0~n-1)。如果仍有多解,移动序列((L)代表左,(R)代表右)的字典序应最小。

技术图片

分析:

简单的(对大佬来说)计数DP。

我们设状态(d[i][j][k])表示从坐标为((i,j))的点到达最后一层的和为(k)的方案数。

因为它只能往左下或者右下走,所以我们不难得出状态转移方程为:
[ d[i][j][k]=d[i+1][j][k-num[i][j]]+d[i+1][j+1][k-num[i][j] ]
注意以上方程只针对于行数编号大于等于(n)的时候,因为我们发现在前(n)行中的转移应该是
[ d[i][j][k]=d[i+1][j-1][k-num[i][j]]+d[i+1][j][k-num[i][j]] ]
然后进行转移即可。(注:有些博客上把状态转移方程中的(val)((也即(num[i][j]))解释错了,可能是笔误)

Code:

/*
    Problem ID:UVa 10564
    Author: dolires
    Date: 30/09/2019 09:16 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;

const int maxn=25;

ll d[maxn][maxn][510];
int n;
int S;
int num[maxn<<1][maxn];

template<class T>void read(T &x)
{
    bool f=0;char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}

void print(int i,int j,int sum)
{
    if(i>=2*n-1) return;//注意这里是等于时就要退出,因为在上一次递归时就已经输出了这一步的策
    //略
    if(i<n)
    {
        if(j>1&&d[i+1][j-1][sum-num[i][j]])
        {
            printf("L");//这里就已经输出了下一步是往左走还是往右走
            print(i+1,j-1,sum-num[i][j]);
        }
        else
        {
            printf("R");
            print(i+1,j,sum-num[i][j]);
        }
        return;
    }
    else
    {
        if(d[i+1][j][num[i][j]])
        {
            printf("L");
            print(i+1,j,sum-num[i][j]);
        }
        else
        {
            printf("R");
            print(i+1,j+1,sum-num[i][j]);
        }
    }
}
int main()
{
    read(n);read(S);
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=n-i+1;++j)
        {
            read(num[i][j]);
        }
    }
    for(int i=n+1;i<=2*n-1;++i)
    {
        for(int j=1;j<=i-n+1;++j)
        {
            read(num[i][j]);
        }
    }
    for(int i=1;i<=n;++i)
    {
        d[2*n-1][i][num[2*n-1][i]]=1;
    }
    for(int i=2*n-2;i>=n;--i)
    {
        for(int j=1;j<=i+1-n;++j)
        {
            for(int k=num[i][j];k<=S;++k)
            {
                d[i][j][k]=d[i+1][j][k-num[i][j]]+d[i+1][j+1][k-num[i][j]];
            }
        }
    }
    ll ans=0;
    for(int i=n-1;i>=1;--i)
    {
        for(int j=1;j<=n-i+1;++j)
        {
            for(int k=num[i][j];k<=S;++k)
            {
                if(j>1) d[i][j][k]+=d[i+1][j-1][k-num[i][j]];
                if(j<n-i+1) d[i][j][k]+=d[i+1][j][k-num[i][j]];//之所以加特判是因为在
                //上半部分的时候,越上面列数越多
            }
            if(i==1) ans+=d[1][j][S];
        }
    }
    printf("%lld
",ans);
    for(int i=1;i<=n;++i)
    {
        if(d[1][i][S])
        {
            printf("%d ",i-1);//因为是从0开始编号的
            print(1,i,S);
            break;
        }
    }
    printf("
");
    return 0;
}

后序:

很多时候我看着网上题解放的代码看起来很长,所以不愿意去写,就比如说这一篇,但是当你真正理解了这一道题的时候,代码虽然看起来长,但是写起来都是很容易的。

11.Headmaster‘s Headache,UVa 10817

题意:

某校有(n)个教师和(m)个求职者。已知每人的工资和能教的课程集合,要求支付最少的工资使得每门课都至少有两名老师教学。在职教师必须招聘。

科目个数(1<=s<=8),在职教师人数(1<=m<=20)和申请者个数(1<=n<=100)

分析:

在紫书上这是一道例题,而且当时我还熬夜看这道题的题解,看不懂就在一直看代码,所以印象比较深刻,应该可以自己试着把它打出来。

因为要使每门课都至少有两个老师教,那么每门课的状态就只有三种:

没有老师教,有一个老师教,有至少两个老师教。

再看看数据范围(数据范围很多时候往往就是会提醒你正解是什么),一看就知道是状态压缩动态规划嘛。

那我们现在就设(d[i][s1][s2])表示前(i)个人,状态集合为(S1)的科目有(1)个老师教,状态集合为(S2)的科目有两个老师教的时候至少需要给的工资(多于2个老师教把它记在(k=2)的集合状态中)。

因为已知有一个老师教的科目和有两个老师教的科目,就可以直接计算出没有老师教的科目,所以没必要再设这一维的状态。

对于一个老师,如果他是在职教师,就只能够有一种决策,那就是选他,否则的话对于求职者,你可以选择选他或者是不选他。

然后通过他们的能教课程的集合来更新状态,详细的实现情况请关键代码。

Code:

/*
    Problem ID:UVa 10817
    Author: dolires
    Date: 29/09/2019 22:22
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

const int maxn=10;
const int inf=0x7fffffff;

int d[200][1<<maxn][1<<maxn];
int teach[200];
int salary[200];

template<class T>void read(T &x)
{
    bool f=0;char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}

int n,s,m;
char c[maxn];
int dp(int i,int s0,int s1,int s2)
{
    if(i==m+n+1) return s2==(1<<s)-1?0:inf;
    if(d[i][s1][s2]>=0) return d[i][s1][s2];
    int &ans=d[i][s1][s2];
    ans=inf;
    if(i>m) ans=dp(i+1,s0,s1,s2);//不聘用这位老师
    int m0=s0&teach[i];
    int m1=s1&teach[i];
    s0^=m0;
    s1=(s1^m1)|m0;
    s2|=m1;
    ans=min(ans,salary[i]+dp(i+1,s0,s1,s2));
    return ans;
}
int main()
{
    read(s);read(m);read(n);
    for(int i=1;i<=m;++i)
    {
        cin>>c;
        for(int j=0;j<s;++j)
        {
            if(c[j]=='1') teach[i]|=(1<<j);
        }
    }
    for(int i=m+1;i<=m+n;++i)
    {
        cin>>c;
        for(int j=0;j<s;++j)
        {
            if(c[j]=='1') teach[i]|=(1<<j);
        }
    }
    memset(d,-1,sizeof(d));
    printf("%d
",dp(1,0,0,0));
    return 0;
}

注:这里的输入不太满足要求,我主要是因为想练一练状压DP的部分的写法,如果对于输入有要求的同学,可以上网参考一下别的大佬的博客。

11.Strategic Game,SEERC 2000,LA 2038

题意:

给定一棵树,选择尽量少的结点,使得每个没有选中的节点至少和一个已选结点相邻。

分析:

一道树形DP的基础题(太简单了)。

没什么好写的。

还是写一点吧。

如果不选这个节点,那么一定要选它的子结点,如果选了这个节点,可以选它的子结点也可以不选。

Code:

/*
    Problem ID:LA 2038
    Author: dolires
    Date: 30/09/2019 09:49 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

const int maxn=1510;

int d[maxn][3];

template<class T>void read(T &x)
{
    bool f=0;char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}

int head[maxn],tot;
bool vis[maxn];
struct Edge
{
    int to,nxt;
    Edge(){};
    Edge(int to,int nxt):to(to),nxt(nxt){};
}ed[maxn<<1];
void add(int u,int v)
{
    ed[++tot]=Edge(v,head[u]);
    head[u]=tot;
    ed[++tot]=Edge(u,head[v]);
    head[v]=tot;
}
void dp(int u)
{
    d[u][0]=0,d[u][1]=1;
    vis[u]=true;
    for(int i=head[u];i;i=ed[i].nxt)
    {
        int v=ed[i].to;
        if(vis[v]) continue;
        dp(v);
        d[u][0]+=d[v][1];
        d[u][1]+=min(d[v][0],d[v][1]);
    }
}
int main()
{
    int n;
    read(n);
    for(int i=1;i<n;++i)
    {
        int u,v;
        read(u);read(v);
        add(u,v);//其实都没必要用链式前向星存边的
    }
    dp(1);
    printf("%d
",min(d[1][0],d[1][1]));
    return 0;
}

至此,我已经把蓝书上所说的动态规划基础题做完了。

所以完结撒花。

不,还有进阶版。

请移步动态规划(四)继续观看,

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

是否可以动态编译和执行 C# 代码片段?

动态规划_线性动态规划,区间动态规划

应对笔试手写代码,如何准备动态规划?

应对笔试手写代码,如何准备动态规划?

应对笔试手写代码,如何准备动态规划?

算法动态规划 ⑤ ( LeetCode 63.不同路径 II | 问题分析 | 动态规划算法设计 | 代码示例 )