动态规划
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;
}
至此,我已经把蓝书上所说的动态规划基础题做完了。
所以完结撒花。
不,还有进阶版。
请移步动态规划(四)继续观看,
以上是关于动态规划的主要内容,如果未能解决你的问题,请参考以下文章