思想
有些问题可以抽象为一个有向无环图,再进行求最短路,最长路或者路径计数问题,求得原问题的解.
模型
矩形嵌套问题:
描述有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。
输入:
第一行是一个正正数N(0<N<10),表示测试数据组数,
每组测试数据的第一行是一个正正数n,表示该组测试数据中含有矩形的个数(n<=1000)
随后的n行,每行有两个数a,b(0<a,b<100),表示矩形的长和宽
输出:
每组测试数据都输出一个数,表示最多符合条件的矩形数目,每组输出占一行
样例输入:
1
10
1 2
2 4
5 8
6 10
7 9
3 1
5 8
12 10
9 7
2 2
样例输出:
5
分析
将矩形之间的可嵌套关系抽象成二元关系,矩形x可以嵌套 在矩形y里面,就从x到y连一条有向边,且这个有向图是无环的.这样这个问题便转化为了求图上的长路径.
代码(只考虑单组输入)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct Mat{
int x;
int y;
}mat[100]; // 矩形结构体,大小视具体题目规模而定
int n, x, y;
int a[100][100], dis[100]; // a数组为邻接矩阵,dis数组存储路径长度
int DP(int i) { // 这里用记忆化搜索来做,参数传起点
if(dis[i] > 0) // 大于0说明已经确定了路径,直接返回,节省时间
return dis[i];
dis[i] = 1; // 别忘了这里的赋值
for(int j = 0; j < n; j++) {
if(a[i][j] == 1) // 两点有i到j的边
dis[i] = max(dis[i], DP(j)+1); // 取当前值和到i前的一个点加1的最大值
}
return dis[i];
}
void Print(int i) { // 字典序输出
cout << i << " ";
for(int j = 1; j <= n; j++)
if(a[i][j] && dis[i] == dis[j]+1) {
Print(j);
break;
}
}
int main() {
cin >> n;
for(int i = 0; i < n; i++) {
cin >> mat[i].x >> mat[i].y;
}
// 建图
for (int i = 0; i < n; i++) {
for(int j = i+1; j < n; j++) {
if((mat[i].x < mat[j].x && mat[i].y < mat[j].y) || (mat[i].x < mat[j].y && mat[i].y < mat[j].x))
a[i][j] = 1;
if((mat[j].x < mat[i].x && mat[j].y < mat[i].y) || (mat[j].x < mat[i].y && mat[j].y < mat[i].x))
a[j][i] = 1;
}
}
int Max = 0;
// 没有确定起点终点,就挨着找呗
for (int i = 1; i <= n; i++) {
if(DP(Max) < DP(i))
Max = i;
}
cout << dis[Max] << endl;
Print(Max);
return 0;
}
最少硬币问题
Description
设有n种不同面值的硬币,各硬币的面值存于数组T[1:n]中。现要用这些面值的硬币来找钱。可以使用的各种面值的硬币个数存于数组Coins[1:n]中。 对任意钱数0≤m≤20001,设计一个用最少硬币找钱m的方法。 对于给定的1≤n≤10,硬币面值数组T和可以使用的各种面值的硬币个数数组Coins,以及钱数m,0≤m≤20001,计算找钱m的最少硬币数。
Input
输入数据第一行中只有1个整数给出n的值,第2行起每行2个数,分别是 T[j]和Coins[j]。最后1行是要找的钱数m。
Output
输出数据只有一个整数,表示计算出的最少硬币数。问题无解时输出-1。
Sample Input
3
1 3
2 3
5 3
18
Sample Output
5
分析
紫书上说把这个问题抽象成从s(all)点到0点的有向图,但是直接按问题的原本描述来做也不难理解.
代码
#include <bits/stdc++.h>
using namespace std;
const int INF = 1000000;
int n, all; // n硬币种类数,all需要换的总钱数
// dp[i]表示i元钱时的所需的最少硬币数,v数组存储面值,t数组存储对应面值的钱数目
int dp[20005], v[20], t[20005];
void Print(int i) { // 还是字典序输出
for(int j = 1; j <= n; j++) {
if(i >= v[j] && dp[i-v[j]] + 1 == dp[i]) {
cout << v[j] << " ";
Print(i-v[j]);
break;
}
}
}
int main() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> v[i];
cin >> t[i];
}
cin >> all;
for(int i = 1; i <= 20005; i++) {
dp[i] = INF; // 初值都赋为最大
}
dp[0] = 0; // 重要的事情说三遍重要的事情说三遍重要的事情说三遍
for(int i = 1; i <= n; i++) // 遍历每一种面值的硬币
for(int j = 1; j <= t[i]; j++) // 遍历个数
for(int k = all; k >= v[i]; k--)
dp[k] = min(dp[k], dp[k-v[i]]+1);
if(dp[all] == INF) // 没找到方案
cout << -1;
else
cout << dp[all];
cout << endl;
Print(all); // 光解题不用写这个
return 0;
}