吾日三省吾身,刷题否,刷题否,刷题否
目录:
- 1.最小花费(动态规划/Dikjstra最短路)
- 2.放苹果(动态规划)
-
1.一省
1.1题目描述
在某条线路上有N个火车站,有三种距离的路程,L1,L2,L3,对应的价格为C1,C2,C3.其对应关系如下: 距离s :票价 0<S<=L1 : C1 L1<S<=L2:C2 L2<S<=L3:C3 输入保证0<L1<L2<L3< 1 0 9 10^9 109,0<C1<C2<C3< 1 0 9 10^9 109。 每两个站之间的距离不超过L3。 当乘客要移动的两个站的距离大于L3的时候,可以选择从中间一个站下车,然后买票再上车,所以乘客整个过程中至少会买两张票。 现在给你一个 L1,L2,L3,C1,C2,C3。然后是A B的值,其分别为乘客旅程的起始站和终点站。 然后输入N,N为该线路上的总的火车站数目,然后输入N-1个整数,分别代表从该线路上的第一个站,到第2个站,第3个站,……,第N个站的距离。 根据输入,输出乘客从A到B站的最小花费。
输入描述:
以如下格式输入数据:
L1 L2 L3 C1 C2 C3
A B
N
a[2]
a[3]
……
a[N]
输出描述:
可能有多组测试数据,对于每一组数据,
根据输入,输出乘客从A到B站的最小花费。
示例1
输入
1 2 3 1 2 3
1 2
2
2
输出
2
1.2基本思路
主要与两种思路:
①我一开始想到的是第一种思路:首先根据分段函数通过两个结点之间的距离计算出相邻的结点之间的代价,然后将结点之间的代价作为权值利用邻接链表进行存储,随后得到图的完整表示。最后利用Dijkstra最短路算法求解得到结点a和b之间的最短路径长度。
②这道题想要少写些代码快速的解决掉,还是得利用动态规划。由于a≤b,那么其实可以定义规划数组dp[N],其中dp[i]表示从a到结点i的最小花费。可以得到以下递推关系式:
d
p
[
i
]
=
min
a
≤
j
≤
i
{
d
p
[
j
]
+
c
o
s
t
<
j
,
i
>
}
dp[i]=\min\limits_{a≤j≤i}\{{dp[j]+cost<j,i>}\}
dp[i]=a≤j≤imin{dp[j]+cost<j,i>}
其中
c
o
s
t
<
j
,
i
>
cost<j,i>
cost<j,i>为结点j到结点i之间的费用。不难发现,这个递推关系式和最长递增子序列问题有点像。
1.3代码实现
Dijkstra
#include <iostream>
#include <climits>
#include <vector>
#define N 101
using namespace std;
struct E{
int node;
int cost;
};
int L[3],C[3];
int dis[N];
int shortest[N];//记录结点a到结点b的最短距离
bool mark[N];
vector<E> V[101];//存储图的结构
int main()
{
while(~scanf("%d%d%d%d%d%d",&L[0],&L[1],&L[2],&C[0],&C[1],&C[2])){
int a,b,n;
scanf("%d%d",&a,&b);
scanf("%d",&n);
dis[1]=0;
for(int i=2;i<=n;i++){
scanf("%d",&dis[i]);
}
for(int i=1;i<=n;i++){
V[i].clear();//清空图
}
E tmp;
for(int i=1;i<=n;i++){//利用邻接链表存储图的结构
for(int j=i+1;j<=n;j++){
if(dis[j]-dis[i]<=L[0]){
tmp.node=j;
tmp.cost=C[0];
V[i].push_back(tmp);
tmp.node=i;
V[j].push_back(tmp);
}
else if(dis[j]-dis[i]<=L[1]){
tmp.node=j;
tmp.cost=C[1];
V[i].push_back(tmp);
tmp.node=i;
V[j].push_back(tmp);
}
else if(dis[j]-dis[i]<=L[2]){
tmp.node=j;
tmp.cost=C[2];
V[i].push_back(tmp);
tmp.node=i;
V[j].push_back(tmp);
}
else
break;//一旦出现到不了的结点,后续的结点都无法到达
}
}
//initial
for(int i=1;i<=n;i++){
mark[i]=false;
shortest[i]=INT_MAX;
}
//Dijkstra
int newP=a;//将起始点加入集合K
shortest[newP]=0;
mark[newP]=true;
for(int k=1;k<=n-1;k++){//还剩下其余的n-1个结点需要进行选择
for(int i=0;i<V[newP].size();i++){//遍历所有与newP相邻的结点
if(mark[V[newP][i].node])
continue;
if(shortest[newP]+V[newP][i].cost<shortest[V[newP][i].node]||shortest[V[newP][i].node]==INT_MAX){
shortest[V[newP][i].node]=shortest[newP]+V[newP][i].cost;
}
}
int min=INT_MAX;
for(int i=1;i<=n;i++){//查询不属于集合k的最近点
if(mark[i]||shortest[i]==INT_MAX)continue;
if(shortest[i]<min){
min=shortest[i];
newP=i;
}
}
mark[newP]=true;
}
printf("%d\n",shortest[b]);
}
return 0;
}
动态规划
#include <iostream>
#include <climits>
#define N 101
using namespace std;
int n,a,b;
int L[3],C[3];
int dis[N];
int dp[N];//dp[i]表示从a->i的最下花费
int getCost(int dist){
if(dist<=L[0])return C[0];
else if(dist<=L[1]) return C[1];
else if(dist<=L[2]) return C[2];
}
int main()
{
while(~scanf("%d%d%d%d%d%d",&L[0],&L[1],&L[2],&C[0],&C[1],&C[2])){
scanf("%d%d",&a,&b);
scanf("%d",&n);
for(int i=1;i<=n;i++)dp[i]=INT_MAX;
dis[1]=0;
dp[a]=0;
for(int i=2;i<=n;i++)
scanf("%d",&dis[i]);
for(int i=a;i<=b;i++){
for(int j=a;j<=i;j++){
if(dis[i]-dis[j]<=L[2]){//表示i与j之间连通
if(dp[j]+getCost(dis[i]-dis[j])<dp[i])
dp[i]=dp[j]+getCost(dis[i]-dis[j]);
}
}
}
printf("%d\n",dp[b]);
}
return 0;
}
2.二省
2.1题目描述:
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
输入描述:
每行均包含二个整数M和N,以空格分开。1<=M,N<=10。
输出描述:
对输入的每组数据M和N,用一行输出相应的K。
示例1
输入
7 3
输出
8
2.2基本思路
对于该问题需要采用动态规划的思想进行求解,一开始思路打偏了,一直想用排列组合的方法,想了好久一直找不到一个合适的解决方案。
以下为动态规划的思路:
dp(m,n)表示m个苹果,n个盘子的方法。但m<n时,即至多只有m个盘子里面有苹果,去掉剩余的盘子也没关系,所以为dp(m,m)。当m≥n时,存在两种情况①至少存在一个空盘子此时返回dp(m,n-1)②不存在空盘子,那么当前的状态与每个盘子上面都拿掉一个苹果等价,即此时返回F(m-n,n).综上,m≥n时dp(m,n)=dp(m,n-1)+dp(m-n,n); 最后考虑初始化的过程,可以明显的知道,当m0或者n=1的时候都只有一种情况。该问题可以采用递归和迭代两种方法进行求解。综上所述可以得到以下递推方程:
d
p
[
n
]
[
m
]
=
{
1
,
m
=
0
o
r
n
=
1
d
p
[
m
]
[
m
]
,
m
<
n
d
p
[
m
,
n
−
1
]
+
d
p
[
m
−
n
,
n
]
,
m
≥
n
dp[n][m]= \begin{cases} 1, m=0\ or \ n=1\\ dp[m][m],m<n\\ dp[m,n-1]+dp[m-n,n],m≥n\\ \end{cases}
dp[n][m]=⎩⎪⎨⎪⎧1,m=0 or n=1dp[m][m],m<ndp[m,n−1]+dp[m−n,n],m≥n
2.3代码实现
#include <iostream>
#define N 11
using namespace std;
int F(int m,int n){//递归的求解子问题
if(m==0||n==1)//注意题目虽然说了M≥1,但递归出口出必须定义m==0的情况
return 1;
else if(m<n){
return F(m,m);
}
else
return F(m,n-1)+F(m-n,n);
}
int main()
{
int m,n;
int dp[N][N];
while(~scanf("%d%d",&m,&n)){
for(int i=0;i<=n;i++)dp[0][i]=1;
for(int i=0;i<=m;i++)dp[i][1]=1;
for(int i=1;i<=m;i++){//采用迭代的方法进行求解
for(int j=2;j<=n;j++){//注意此处n应该从2开始
if(i<j)
dp[i][j]=dp[i][i];
else
dp[i][j]=dp[i][j-1]+dp[i-j][j];
}
}
printf("%d\n",dp[m][n]);
}
return 0;
}