L2-001 紧急救援 最短路+路径打印
作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。
输入格式:
输入第一行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0 ~ (N−1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。
第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。
输出格式:
第一行输出最短路径的条数和能够召集的最多的救援队数量。第二行输出从S到D的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。
输入样例:
4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2
输出样例:
2 60
0 1 3
分析:
Dijkstra基础应用,比模板单纯求最短路的基础上多了输出路径,路径条数以及多权重(路径相同时人数尽量大),路径只要存每个节点的前驱,然后倒着遍历一遍就行。其他就是一些小细节,看注释。优先队列默认为大顶堆priority_queue<PII>
,这里需要改成小顶堆priority_queue<PII,vector<PII>,greater<PII> >
代码:
#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
const int INF = 0x3f3f3f3f;
const int N = 510;
//n个点m条边从s到d
int n,m,s,d;
//分别为:邻接矩阵存图,是否访问过,到起点的最短距离。
vector<PII> mp[N];
int vis[N], dis[N];
//分别为:到当前点最多召集几个人,每个城市的人数,存路径,到当前点几条路。
int tot[510], city[510], path[510], road[510];
void DJ(int s){
//初始化
for(int i = 1 ; i <= n ; i ++ )
dis[i] = INF, vis[i] = false, path[s] = -1;
tot[s] = city[s];
dis[s] = 0;
road[s] = 1;
//小顶堆
priority_queue<PII, vector<PII>, greater<PII> > q;
q.push({
0, s});
while(q.size()){
int t = q.top().second;
q.pop();
if(vis[t]) continue;
vis[t] = true;
for(int i = 0 ; i < mp[t].size() ; i ++ ){
int j = mp[t][i].first, w = mp[t][i].second;
if(dis[j] > dis[t]+w){
//松弛操作
//j从t这个点过来的路径更短,前驱变为t
path[j] = t;
//从t走过来,则可以召集的人数就是到t可召集的人数+j这个点的人数
tot[j] = tot[t]+city[j];
dis[j] = dis[t]+w;
//到t有几种走法,就到j有几种
road[j] = road[t];
//由于j点最短距离被更新,需要压入队列
q.push({
dis[j], j});
}
else if(dis[j] == dis[t]+w){
//有另外的走法使得最短路相同,更新方案数
road[j] += road[t];
//如果可以召集更多的人,更新方案
if (tot[j] < tot[t] + city[j]) {
tot[j] = tot[t] + city[j];
path[j] = t;
}
}
}
}
}
void print(int d){
//路径打印
int now = d;
vector<int> v;
while(now != -1){
v.push_back(now);
now = path[now];
}
for (int i = v.size()-1 ; i >= 0 ; i -- )
printf("%d%c", v[i], i==0 ? '\n' : ' ');
}
int main(){
cin>>n>>m>>s>>d;
for(int i = 0 ; i < n ; i ++ ) cin>>city[i];
for(int i = 0 ; i < m ; i ++ ){
int x, y, w;
cin>>x>>y>>w;
mp[x].push_back({
y, w});
mp[y].push_back({
x, w});
}
DJ(s);
cout<<road[d]<<" "<<tot[d]<<endl;
print(d);
return 0;
}
L2-002 链表去重 模拟链表
给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉。即对每个键值 K,只有第一个绝对值等于 K 的结点被保留。同时,所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15,你需要输出去重后的链表 21→-15→-7,还有被删除的链表 -15→15。
输入格式:
输入在第一行给出 L 的第一个结点的地址和一个正整数 N(≤105,为结点总数)。一个结点的地址是非负的 5 位整数,空地址 NULL 用 −1 来表示。
随后 N 行,每行按以下格式描述一个结点:
地址 键值 下一个结点
其中地址
是该结点的地址,键值
是绝对值不超过104的整数,下一个结点
是下个结点的地址。
输出格式:
首先输出去重后的链表,然后输出被删除的链表。每个结点占一行,按输入的格式输出。
输入样例:
00100 5
99999 -7 87654
23854 -15 00000
87654 15 -1
00000 -15 99999
00100 21 23854
输出样例:
00100 21 23854
23854 -15 99999
99999 -7 -1
00000 -15 87654
87654 15 -1
分析:
数据量不大,可以直接用数组模拟链表,地址当做数组下标,考察基本的删除操作和尾插法。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
struct node{
int id,val,next,absval;
}LNode[N];
bool cmp(node a,node b){
return a.id<b.id;
}
pair<int,pair<int,int> > p;
vector<pair<int,pair<int,int> > > com,del;
int f[N];
int main(){
int ID,N,i;
cin>>ID>>N;
for(i=0;i<N;i++){
cin>>LNode[i].id>>LNode[i].val>>LNode[i].next;
LNode[i].absval=fabs(LNode[i].val);
f[abs(LNode[i].val)]=0; //避免二分时重复计算
}
sort(LNode,LNode+N,cmp);
while(ID!=-1){
//用-1结束,别用N计数
int l=0,r=N-1; //二分查找下一节点
while(l<r){
int mid=(l+r)/2;
if(LNode[mid].id<ID) l=mid+1;
else r=mid;
}
p.first=LNode[l].id;
p.second.first=LNode[l].val;p.second.second=LNode[l].next;
if(f[LNode[l].absval]==0){
f[LNode[l].absval]=1;
com.push_back(p);
}
else
del.push_back(p);
ID=LNode[l].next;
}
for(i=0;i<com.size();i++){
if(i<com.size()-1)
printf("%05d %d %05d\n",com[i].first,com[i].second.first,com[i+1].first);
else
printf("%05d %d -1\n",com[i].first,com[i].second.first);
}
for(i=0;i<del.size();i++){
if(i<del.size()-1)
printf("%05d %d %05d\n",del[i].first,del[i].second.first,del[i+1].first);
else
printf("%5d %d -1\n",del[i].first,del[i].second.first);
}
return 0;
}
L2-003 月饼 贪心
月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量,请你计算可以获得的最大收益是多少。
注意:销售时允许取出一部分库存。样例给出的情形是这样的:假如我们有 3 种月饼,其库存量分别为 18、15、10 万吨,总售价分别为 75、72、45 亿元。如果市场的最大需求量只有 20 万吨,那么我们最大收益策略应该是卖出全部 15 万吨第 2 种月饼、以及 5 万吨第 3 种月饼,获得 72 + 45/2 = 94.5(亿元)。
输入格式:
每个输入包含一个测试用例。每个测试用例先给出一个不超过 1000 的正整数 N 表示月饼的种类数、以及不超过 500(以万吨为单位)的正整数 D 表示市场最大需求量。随后一行给出 N 个正数表示每种月饼的库存量(以万吨为单位);最后一行给出 N 个正数表示每种月饼的总售价(以亿元为单位)。数字间以空格分隔。
输出格式:
对每组测试用例,在一行中输出最大收益,以亿元为单位并精确到小数点后 2 位。
输入样例:
3 20
18 15 10
75 72 45
输出样例:
94.50
分析:
按照单价排序,优先选择单价最高的。库存量和总售价不一定为整数
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
struct node{
double num,price,dj;
}Mooncake[N];
bool cmp(node a,node b){
return a.dj>b.dj; }
int main(){
int n,m,k=0,i;
cin>>n>>m;
for(i=0;i<n;i++) cin>>Mooncake[i].num;
for(i=0;i<n;i++) cin>>Mooncake[i].price;
for(i=0;i<n;i++) Mooncake[i].dj=Mooncake[i].price/Mooncake[i].num;
sort(Mooncake,Mooncake+n,cmp);
double ans=0;
while(k<n){
if(Mooncake[k].num<=m){
m-=Mooncake[k].num;
ans+=Mooncake[k].price;
}
else{
ans+=Mooncake[k].dj*m;
break;
}
k++;
}
printf("%.2lf\n",ans);
return 0;
}
L2-004 这是二叉搜索树吗? 数据结构
一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点,
- 其左子树中所有结点的键值小于该结点的键值;
- 其右子树中所有结点的键值大于等于该结点的键值;
- 其左右子树都是二叉搜索树。
所谓二叉搜索树的“镜像”,即将所有结点的左右子树对换位置后所得到的树。
给定一个整数键值序列,现请你编写程序,判断这是否是对一棵二叉搜索树或其镜像进行前序遍历的结果。
输入格式:
输入的第一行给出正整数 N(≤1000)。随后一行给出 N 个整数键值,其间以空格分隔。
输出格式:
如果输入序列是对一棵二叉搜索树或其镜像进行前序遍历的结果,则首先在一行中输出 YES
,然后在下一行输出该树后序遍历的结果。数字间有 1 个空格,一行的首尾不得有多余空格。若答案是否,则输出 NO
。
输入样例 1:
7
8 6 5 7 10 8 11