题目
思路
DAG的固定起点和终点的最短路和最航路问题。
0.抽象:初始状态为S,末状态为0,每次使用一个硬币,状态由S’变为S’-V[i]。为DAG。
1.状态及指标函数定义:mind[i]:0->i最短路长度,maxd[i]:0->i最长路长度。
2.状态转移方程:
mind(i)=min{mind(i−Vx)+1,x∈[0,n)}
m
i
n
d
(
i
)
=
m
i
n
{
m
i
n
d
(
i
−
V
x
)
+
1
,
x
∈
[
0
,
n
)
}
maxd(i)=max{maxd(i−Vx)+1,x∈[0,n)}
m
a
x
d
(
i
)
=
m
a
x
{
m
a
x
d
(
i
−
V
x
)
+
1
,
x
∈
[
0
,
n
)
}
3.代码实现时,特殊值的使用:
d[i] = -1 : 尚未计算。
d[i] < 0 || d[i] > INF : 计算了,但无论如何都走不到终点。
d[i] = 0或其它 : 计算了,正常值。
4.代码实现采用两种方法:记忆化搜索与递推。
总体来说递推稍快,因为记忆化搜索需要调用系统栈,而递推只是单纯循环。
- 对于记忆化搜索的输出方法,采用“类记忆化搜索”。
- 对于递推的输出方案,采用“递推顺带记录输出数据”。
代码
1.记忆化搜索,输出ans
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define _for(i,a,b) for (int i =(a); i<(b); i++)
using namespace std;
const int maxn = 1000 + 10;
const int INF = 1 << 30;
int n, S, V[maxn], d[maxn];
int dp1(int x) {
int &ans = d[x];
if (ans != -1) return ans; //未被访问过
ans = INF; //意思是,无法达到
_for(i, 0, n)
if (x - V[i] >= 0)
ans = min(ans, dp1(x - V[i]) + 1);
return ans;
}
int dp2(int x) {
int &ans = d[x];
if (ans != -1) return ans; //未被访问过
ans = -INF; //意思是,无法达到
_for(i, 0, n)
if (x - V[i] >= 0)
ans = max(ans, dp2(x - V[i]) + 1);
return ans;
}
int main() {
scanf("%d%d", &n, &S);
_for(i, 0, n) scanf("%d", &V[i]);
memset(d, -1, sizeof(d));
d[0] = 0;
int minans = dp1(S);
if (minans >= INF) minans = -1;
memset(d, -1, sizeof(d));
d[0] = 0;
int maxans = dp2(S);
if (maxans < 0) maxans = -1;
printf("%d %d\n", minans, maxans);
return 0;
}
2.递推,输出ans
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define _for(i,a,b) for (int i =(a); i<(b); i++)
#define _rep(i,a,b) for (int i =(a); i<=(b); i++)
using namespace std;
const int maxn = 1000 + 10;
const int INF = 1 << 30;
int n, S, V[maxn], mind[maxn], maxd[maxn];
int main() {
scanf("%d%d", &n, &S);
_for(i, 0, n) scanf("%d", &V[i]);
_rep(x, 1, S) { // 递推每个元素都会遍历到,所以不用-1做是否访问
mind[x] = INF;
maxd[x] = -INF;
}
mind[0] = 0;
maxd[0] = 0;
_rep(x, 1, S) // 从已知边界的一侧开始递推
_for(i,0,n)
if (x >= V[i]) {
mind[x] = min(mind[x], mind[x - V[i]] + 1);
maxd[x] = max(maxd[x], maxd[x - V[i]] + 1);
}
if (mind[S] >= INF) mind[S] = -1;
if (maxd[S] < 0) maxd[S] = -1;
printf("%d %d\n", mind[S], maxd[S]);
return 0;
}
3.记忆化搜索,输出方案
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define _for(i,a,b) for (int i =(a); i<(b); i++)
using namespace std;
const int maxn = 1000 + 10;
const int INF = 1 << 30;
int n, S, V[maxn], d[maxn];
int dp1(int x) {
int &ans = d[x];
if (ans != -1) return ans; //未被访问过
ans = INF; //意思是,无法达到
_for(i, 0, n)
if (x - V[i] >= 0)
ans = min(ans, dp1(x - V[i]) + 1);
return ans;
}
int dp2(int x) {
int &ans = d[x];
if (ans != -1) return ans; //未被访问过
ans = -INF; //意思是,无法达到
_for(i, 0, n)
if (x - V[i] >= 0)
ans = max(ans, dp2(x - V[i]) + 1);
return ans;
}
void print_ans(int x) {
_for(i,0,n)
if (x >= V[i] && d[x] == d[x - V[i]] + 1) {
printf("%d ", i + 1); // 注意此处是打印边,而不是打印顶点,与矩形嵌套那个题分清
print_ans(x - V[i]);
break;
}
}
int main() {
scanf("%d%d", &n, &S);
_for(i, 0, n) scanf("%d", &V[i]);
memset(d, -1, sizeof(d));
d[0] = 0;
int minans = dp1(S);
if (minans >= INF) printf("impossible\n");
else { print_ans(S); printf("\n"); }
memset(d, -1, sizeof(d));
d[0] = 0;
int maxans = dp2(S);
if (maxans < 0) printf("impossible\n");
else { print_ans(S); printf("\n"); }
return 0;
}
4.递推,输出方案
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define _for(i,a,b) for (int i =(a); i<(b); i++)
#define _rep(i,a,b) for (int i =(a); i<=(b); i++)
using namespace std;
const int maxn = 1000 + 10;
const int INF = 1 << 30;
int n, S, V[maxn], mind[maxn], maxd[maxn], min_coin[maxn], max_coin[maxn];
void print_ans(int *coin,int x) {
while (x) {
printf("%d ", coin[x]+1);
x -= V[coin[x]];
}
}
int main() {
scanf("%d%d", &n, &S);
_for(i, 0, n) scanf("%d", &V[i]);
_rep(x, 1, S) { // 递推每个元素都会遍历到,所以不用-1做是否访问
mind[x] = INF;
maxd[x] = -INF;
}
mind[0] = 0;
maxd[0] = 0;
_rep(x, 1, S) // 从已知边界的一侧开始递推
_for(i, 0, n)
if (x >= V[i]) {
if (mind[x] > mind[x - V[i]] + 1) { // 此处不是>=是因为要按字典序输出方案
mind[x] = mind[x - V[i]] + 1;
min_coin[x] = i;
}
if (maxd[x] < maxd[x - V[i]] + 1) {
maxd[x] = maxd[x - V[i]] + 1;
max_coin[x] = i;
}
}
if (mind[S] >= INF) printf("impossible\n");
else { print_ans(min_coin, S); printf("\n"); }
if (maxd[S] < 0) printf("impossible\n");
else { print_ans(max_coin, S); printf("\n"); }
return 0;
}