码迷,mamicode.com
首页 > 其他好文 > 详细

动态规划(三)

时间:2019-11-09 23:57:22      阅读:56      评论:0      收藏:0      [点我收藏+]

标签:appdata   int   return   pat   上网   code   起点   spl   oam   

10.Paths through the Hourglass,UVa10564

题意:

有一个沙漏,第一行有\(n\)个格子,第二行有\(n-1\)个格子\(\cdots\cdots\)最中间的行只有1个格子,然后它下面一行2个格子,再下面一行3个格子\(\cdots\cdots\)最后一行\(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\n",ans);
    for(int i=1;i<=n;++i)
    {
        if(d[1][i][S])
        {
            printf("%d ",i-1);//因为是从0开始编号的
            print(1,i,S);
            break;
        }
    }
    printf("\n");
    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\n",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\n",min(d[1][0],d[1][1]));
    return 0;
}

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

所以完结撒花。

不,还有进阶版。

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

动态规划(三)

标签:appdata   int   return   pat   上网   code   起点   spl   oam   

原文地址:https://www.cnblogs.com/iwillenter-top1/p/11828140.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有 京ICP备13008772号-2
迷上了代码!