77. 供应链上的最高价(1090)
本题要求求出供应链上的最高售价和个数。实质上统计最深的叶子结点值和个数
思路:
(1)先用树的静态表示存储树结构
(2)从根结点开始按DFS递归遍历,寻找最深结点
#include<iostream>
#include<stdio.h>
#include<vector>
#include<math.h>
using namespace std;
const int maxn=100010;
int n,num=0,depth=-1;
double p,r;
struct Node{
vector<int> child;
}node[maxn];
void DFS(int root,int layer){
if(node[root].child.size()==0){
//抵达叶子结点
if(depth<layer){//发现更深结点
depth=layer;
num=1;
}
else if(depth==layer){
num++;
}
return;
}
for(int i=0;i<node[root].child.size();i++){
DFS(node[root].child[i],layer+1);
}
}
int main(){
scanf("%d%lf%lf",&n,&p,&r);
int parent,root;
for(int i=0;i<n;i++){
scanf("%d",&parent);
if(parent!=-1){
node[parent].child.push_back(i);
}
else{
root=i;
}
}
DFS(root,0);
r/=100;
double hPrice = p*pow(1+r,depth);
printf("%.2f %d",hPrice,num);
return 0;
}
78. 人数最多的一代(1094)
本题本质上是求树中结点最多的一层。解法:
(1)用静态写法存储树,用layer属性存储层号
(2)按层序遍历,每个结点出队时,把它的孩子加入到队列,并且修改相应代数人数遍历完成后,从各代人数中选出最大值即可.
#include<iostream>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
struct Node{
int layer;
vector<int> child;
}node[100];
int num[100]={0};//统计每代人数
void LayerOrder(int root){
queue<int> q;
q.push(root);
node[root].layer=1;
num[1]++;
while(!q.empty()){
int front=q.front();
q.pop();
int child;
for(int i=0;i<node[front].child.size();i++){
child=node[front].child[i];
node[child].layer = node[front].layer+1;
num[node[child].layer]++;
q.push(child);
}
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
int id,k,child;
for(int i=0;i<m;i++){
scanf("%d%d",&id,&k);
for(int j=0;j<k;j++){
scanf("%d",&child);
node[id].child.push_back(child);
}
}
LayerOrder(1);
int max=-1,gen;
for(int i=1;i<100;i++){
if(num[i]>max){
max=num[i];
gen=i;
}
}
printf("%d %d",max,gen);
return 0;
}
79. 供应链上的最低价(1106)
本题要求求出供应链上的最低售价和个数,与1090题非常类似。
(1)先用vector存储每个结点的孩子
(2)从根结点开始按DFS递归遍历,寻找最浅结点
#include<iostream>
#include<stdio.h>
#include<vector>
#include<math.h>
using namespace std;
const int maxn=100010;
int n,depth=maxn,sum=0;
double p,r;
vector<int> node[maxn];
void DFS(int root,int layer){
if(node[root].size()==0){//抵达叶子结点
if(layer<depth){
depth=layer;
sum=1;
}
else if(layer==depth){
sum++;
}
return;
}
for(int i=0;i<node[root].size();i++){
DFS(node[root][i],layer+1);
}
}
int main(){
scanf("%d%lf%lf",&n,&p,&r);
int num,child;
for(int i=0;i<n;i++){
scanf("%d",&num);
for(int j=0;j<num;j++){
scanf("%d",&child);
node[i].push_back(child);
}
}
DFS(0,0);
r/=100;
double lowPrice = p*pow(1+r,depth);
printf("%.4f %d",lowPrice,sum);
return 0;
}
80. 计算叶子(1004)
本题要求计算树的每一层的叶子树,很直观的思路便是按层遍历,遇到子节点数为0的结点,计数+1
#include<iostream>
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
struct Node{
int layer;
vector<int> child;
}node[100];
int num[100]={0};//统计每层叶子结点数
int depth=0;
void LayerOrder(int root){
queue<int> q;
q.push(root);
node[root].layer=0;
while(!q.empty()){
int front = q.front();
q.pop();
if(node[front].child.size()==0){
num[node[front].layer]++;
}
for(int i=0;i<node[front].child.size();i++){
int child=node[front].child[i];
node[child].layer = node[front].layer+1;
if(depth<node[child].layer) depth=node[child].layer;
q.push(child);
}
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
int id,k,child;
for(int i=0;i<m;i++){
scanf("%d%d",&id,&k);
for(int j=0;j<k;j++){
scanf("%d",&child);
node[id].child.push_back(child);
}
}
LayerOrder(1);
for(int i=0;i<=depth;i++){
printf("%d",num[i]);
if(i<depth) printf(" ");
}
return 0;
}
81. 相等权重的路(1053)
本题要求求出所有从根到结点权重和为指定值的路径。
(1)用静态树结构体存储每个结点信息
(2)DFS遍历,寻找权重和相等的路径并输出。
找到路径时如何从头开始输出路径上的结点呢?
考虑用vector存储当前路径上结点,经过某结点时将其存入数组,当该结点的孩子都被访问完时,将其从数组中删除。注意题目还要求按字典序输出因此必须先对孩子结点按权重排序。
#include<iostream>
#include<stdio.h>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
int n,m;
LL s;
struct Node{
LL weight;
vector<int> child;
}node[102];
vector<LL> v;
bool cmp(int x,int y){
//把结点孩子按权重排序
return node[x].weight>node[y].weight;
}
void DFS(int root,LL sum){
sum+=node[root].weight;
//printf("%d\n",sum);
if(sum>s) return;
if(node[root].child.size()==0){//抵达叶子结点
//printf("抵达叶子%d\n",root);
if(sum==s){//找到路径
v.push_back(node[root].weight);
for(int i=0;i<v.size();i++){
printf("%lld",v[i]);
if(i<v.size()-1) printf(" ");
}
printf("\n");
v.pop_back();
}
return;
}
//没到叶子结点,且权重和未达到
v.push_back(node[root].weight);
for(int i=0;i<node[root].child.size();i++){
DFS(node[root].child[i],sum);
}
//遍历完当前结点所有孩子,这个结点不再经过,因此要去除
v.pop_back();
}
int main(){
scanf("%d%d%lld",&n,&m,&s);
for(int i=0;i<n;i++){
scanf("%lld",&node[i].weight);
}
int id,child,k;
for(int i=0;i<m;i++){
scanf("%d%d",&id,&k);
for(int j=0;j<k;j++){
scanf("%d",&child);
node[id].child.push_back(child);
//printf("%d ",child);
}
sort(node[id].child.begin(),node[id].child.end(),cmp);
//printf("\n");
}
DFS(0,0);
return 0;
}
82. 判断二叉搜索树(1043)
给定树的前序序列,判断该树是否是二叉搜索树或者镜像二叉搜索树。以二叉搜索树为例,由于BST结点值具有左<根<右的性质,那么其前序序列中,第一个数字是根值,小于根值的前半部分是左子树,大于根值的后半部分是右子树,若后半部分发现小于根值的值则不是BST。
镜像BST也是类似,只需改变大小关系即可。
#include<iostream>
#include<stdio.h>
using namespace std;
const int maxn=1010;
int num[maxn];
int n,cnt=0;
struct Node{
int data;
int lchild,rchild;
}BST[maxn];
bool flagBST=0,flagMI=0;//表明序列是否是BST或镜像BST
int isBST(int left,int right){
if(left>right) return -1;
int mid=left;//找到划分左右子树区间的位置
for(int i=left+1;i<=right;i++){
if(num[left]>=num[i]){
mid=i;
}
else{
break;
}
}
for(int i=mid+1;i<=right;i++){
if(num[i]<num[left]){
flagBST=1;
return -1;
}
}
int root=left;//根节点
BST[root].lchild = isBST(left+1,mid);
BST[root].rchild = isBST(mid+1,right);
return root;
}
int isMIBST(int left,int right){
if(left>right) return -1;
int mid=left;//找到划分左右子树区间的位置
for(int i=left+1;i<=right;i++){
if(num[left]<=num[i]){
mid=i;
}
else{
break;
}
}
for(int i=mid+1;i<=right;i++){
if(num[i]>num[left]){
flagMI=1;
return -1;
}
}
int root=left;//根节点
BST[root].lchild = isMIBST(left+1,mid);
BST[root].rchild = isMIBST(mid+1,right);
return root;
}
void postOrder(int root){
if(root==-1){
return;
}
postOrder(BST[root].lchild);
postOrder(BST[root].rchild);
printf("%d",BST[root].data);
cnt++;
if(cnt<n) printf(" ");
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
BST[i].data=num[i];
}
//printf("finsh\n");
int root=isBST(0,n-1);
if(flagBST==0){
printf("YES\n");
postOrder(root);
return 0;
}
root = isMIBST(0,n-1);
if(flagMI==0){
printf("YES\n");
postOrder(root);
}
else{
printf("NO\n");
}
return 0;
}
83. 完成二叉搜索树(1064)
给定一串数字,要求用它们构成完全二叉树,并按层序输出。
完全二叉树具有如下性质:任一结点编号为x(从1开始),则其左孩子为2x,右孩子
为2x+1,因此可以用数组存储完全二叉树,且这样就是层序遍历结果。
那么问题关键在于如何确定每个值应该放在什么位置?
思路:先将元素排序,
根据元素个数,确定最底层元素个数,从而确定根节点的大小序号填入,在对左右子树
进行递归。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int maxn=1010;
int n;
int num[maxn],layerOrder[maxn];
void location(int left,int right,int order){
//left/right为当前子树对应num范围,order为当前根存储序号
if(left>right||order>n) return;
int sum = right-left+1;//结点数
int m =(int)(log(sum+1)/log(2));
int full =(int)pow(2,m);
int extra = sum - full+1;//底层元素个数
int root=left-1;
//由于底层元素引起的根序号偏移
if(extra<=full/2) root += full/2 +extra;
else root += full;
layerOrder[order] = num[root];
//printf("root=%d,num=%d\n",root,num[root]);
location(left,root-1,2*order);
location(root+1,right,2*order+1);
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&num[i+1]);
}
sort(num+1,num+n+1);
location(1,n,1);
for(int i=1;i<=n;i++){
printf("%d",layerOrder[i]);
if(i<n) printf(" ");
}
return 0;
}
当然,本题还有更简单的思路,注意到二叉排序树的中序遍历结果是递增的,因此可以中序遍历二叉树,在遍历时将数据填入二叉树。
语法:log函数的使用,由于其默认以自然对数为底,因此对其它底数,需要使用换底公式。
84. 建立二叉搜索树(1064)
给定一串数字,和树的结构,要求把数字正确填入树,使之成为二叉搜索树,
并以层序遍历输出。
思路:
(1)先建立树的静态结构,读入树的结构;
(2)根据有序二叉树中序遍历有序的性质,中序遍历并把排好序的数字填入;
(3)用队列实现层序遍历。
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
struct Node{
int data;
int lchild,rchild;
}node[105];
int num[105];
int n,index=0,sum=0;
void inOrder(int root){
if(root==-1) return;
//对左子树递归
inOrder(node[root].lchild);
node[root].data = num[index++];
//对右子树递归
inOrder(node[root].rchild);
}
void LayerOrder(int root){
queue<int> q;
q.push(root);
while(!q.empty()){
int front = q.front();
q.pop();
sum++;
printf("%d",node[front].data);
if(sum<n) printf(" ");
if(node[front].lchild!=-1) q.push(node[front].lchild);
if(node[front].rchild!=-1) q.push(node[front].rchild);
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d%d",&node[i].lchild,&node[i].rchild);
}
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
}
sort(num,num+n);
inOrder(0);
LayerOrder(0);
return 0;
}
85. 平衡二叉树的根(1066)
给定树的插入序列,要求输出所建立的平衡二叉树的根。思路很简单,根据输入插入到树中,当出现不平衡结点时进行左旋/右旋调整。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
//AVL结构体
struct node{
int v,height;//结点权值和当前子树高度
node *lchild,*rchild;
};
//生成新结点
node* newNode(int v){
node* Node=new node;
Node->v=v;
Node->height=1;//结点初始高度为1
Node->lchild = Node->rchild =NULL;
return Node;
}
//获取结点的当前高度
int getHeight(node* root){
if(root==NULL) return 0;
return root->height;
}
//计算结点的平衡因子
int getBalance(node* root){
//左子树高减去右子树高
return getHeight(root->lchild) - getHeight(root->rchild);
}
//更新高度
void updataHeight(node* root){
root->height = max(getHeight(root->lchild),getHeight(root->rchild))+1;
}
//左旋
void L(node* &root){
node* temp = root->rchild;
root->rchild=temp->lchild;
temp->lchild = root;
updataHeight(root);
updataHeight(temp);
root=temp;
}
//右旋
void R(node* &root){
node* temp = root->lchild;
root->lchild=temp->rchild;
temp->rchild = root;
updataHeight(root);
updataHeight(temp);
root=temp;
}
//将结点插入正确位置
void insert(node* &root,int v){//引用传参,因为这里要修改树的结构
if(root==NULL){//抵达空结点
root = newNode(v);
return;
}
if(v<root->v){//插入左子树
insert(root->lchild,v);
updataHeight(root);//更新树高
if(getBalance(root)==2){//出现不平衡
if(getBalance(root->lchild)==1){//LL型,右旋
R(root);
}
else if(getBalance(root->lchild)==-1){//LR型,先左旋,后右旋
L(root->lchild);
R(root);
}
}
}
else{//插入右子树
insert(root->rchild,v);
updataHeight(root);//更新树高
if(getBalance(root)==-2){//出现不平衡
if(getBalance(root->rchild)==-1){//RR型,左旋
L(root);
}
else if(getBalance(root->rchild)==1){//RL型,先右旋,后左旋
R(root->rchild);
L(root);
}
}
}
}
int main(){
node* root=NULL;
int n,data;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&data);
insert(root,data);
}
printf("%d",root->v);
return 0;
}
86. 社交集群(1066)
给定每个人的爱好,要求出社交集群数量和每个集群人数。当满足下列条件是,A和B共一集群:
(1)如果A和B存在相同爱好,则A和B属于同一集群;
(2)如果A和C一个集群,B和C一个集群,则A和B也同一个集群;
思路是:
(1)根据hobby存储人的序号,相同hobby下的人合并到一个集合中
(2)对每个人找到其所在集群的父节点,把对应集群人数+1;
(3)统计集群数量并输出。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=1010;
int numRoot[maxn]={0};//存储各组人数
int father[maxn];
vector<int> hobby[maxn];
void init(int n){
//初始时,每个人自己是一个社群
for(int i=1;i<=n;i++){
father[i]=i;
}
}
int findFather(int x){
int a=x;
while(x!=father[x]){
x=father[x];
}
//路径压缩,把同一组下父节点统一为根节点
while(a!=father[a]){
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
void Union(int a,int b){
int faA = findFather(a);
int faB = findFather(b);
if(faA!=faB){//B的父节点变为A,实现合并
father[faB]=faA;
//numRoot[faA]++;
}
}
bool cmp(int a, int b){
return a>b;
}
int main(){
int n;
scanf("%d",&n);
init(n);
int num,h;
for(int i=1;i<=n;i++){
scanf("%d: ",&num);
for(int j=0;j<num;j++){
scanf("%d",&h);
hobby[h].push_back(i);
}
}
//按hobby合并社群
for(int i=0;i<maxn;i++){
int len=hobby[i].size();
if(len!=0){
int front=hobby[i][0];
for(int j=1;j<len;j++){
Union(front,hobby[i][j]);
}
}
}
for(int i=1;i<=n;i++){//根据父节点统计各社群人数
//这里应该用findFather(i),避免若集合的父节点在之后被合并到其它集合,
//由于孩子没有被进一步更新父节点,孩子就会被当成另一个集合
numRoot[findFather(i)]++;
//printf("%d的父节点是%d\n",i,father[i]);
}
sort(numRoot+1,numRoot+n+1,cmp);
int numSet=0;
for(int i=1;i<=n;i++){
if(numRoot[i]!=0) numSet++;
}
printf("%d\n",numSet);
for(int i=1;i<=numSet;i++){
printf("%d",numRoot[i]);
if(i<numSet) printf(" ");
}
return 0;
}
87. 插入or堆排序(1098)
本题给定排序的中间情况,要求判断是插入排序还是堆排序。思路也很简单,直接模拟堆排序过程,看是否会与中间结果相同。关键在于掌握:堆的建立和排序。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int num[105],midSort[105],heap[105];
int n;
bool isSame(){
for(int i=1;i<=n;i++){
if(midSort[i]!=heap[i]) return false;
}
return true;
}
//向下调整
void downAdjust(int low,int high){
int i=low,j=i*2;//i为要调整的点,j为其左孩子
while(j<=high){//存在孩子
//若右孩子值大于左孩子,则调整,使j表示最大的孩子
if(j+1<=high && heap[j+1]>heap[j]){
j=j+1;
}
//若大孩子比父结点大
if(heap[j]>heap[i]){
swap(heap[j],heap[i]);//交换值
i=j;//保持i为待调整的点、j为i的左孩子
j=i*2;
}
else{
break;
}
}
}
void createHeap(){
for(int i=n/2;i>=1;i--){
downAdjust(i,n);
}
}
bool heapSort(){
createHeap();
for(int i=n;i>1;i--){
swap(heap[i],heap[1]);
downAdjust(1,i-1);
/*
for(int j=1;j<=n;j++){
printf("%d ",heap[j]);
}
printf("\n");
*/
if(isSame()) {//是堆排序,则再排一轮
swap(heap[i-1],heap[1]);
downAdjust(1,i-2);
return true;
}
}
return false;
}
int main(){
scanf("%d",&n);
//由于这里利用的是堆排序序号从1开始时的,与孩子结点序号关系,
//因此从下标1开始存储
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
heap[i]=num[i];
}
for(int i=1;i<=n;i++){
scanf("%d",&midSort[i]);
}
if(heapSort()){//判断出是堆排序
printf("Heap Sort\n");
for(int i=1;i<=n;i++){
printf("%d",heap[i]);
if(i<n) printf(" ");
}
}
else{
printf("Insertion Sort\n");
for(int i=1;i<n;i++){
if(midSort[i]<midSort[i-1]){
sort(midSort,midSort+i+1);
break;
}
}
for(int i=1;i<=n;i++){
printf("%d",midSort[i]);
if(i<n) printf(" ");
}
}
return 0;
}
88. 城市战争(1013)
本题本质上是从一个无向图中删去某个结点,问需要最少补几条边才能重新构成连通图。
(1)如何确定要补的边数呢?要注意到补的最少边数=连通块数-1;
(2)如何确定要连通块数呢?可以建立并查集,若两个点是连通的,就加入同一连通块;
(3)如何实现结点删除?不必真的删除,在合并集合和统计连通块时都跳过即可。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int maxn=1010;
vector<int> Adj[maxn];//采用邻接表存储
int father[maxn];
bool isRoot[maxn];
int findFather(int x){
int a=x;
while(x!=father[x]){
x=father[x];
}
//路径压缩
while(a!=father[a]){
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
void init(int n){
for(int i=1;i<=n;i++){
father[i]=i;
isRoot[i]=false;
}
}
void Union(int a,int b){
int faA = findFather(a);
int faB = findFather(b);
if(faA!=faB){
father[faA]=faB;
}
}
int main(){
int n,k,m;
scanf("%d%d%d",&n,&m,&k);
int v1,v2;
for(int i=0;i<m;i++){
scanf("%d%d",&v1,&v2);
Adj[v1].push_back(v2);
Adj[v2].push_back(v1);
}
int delVertex;
for(int query=0;query<k;query++){
scanf("%d",&delVertex);
init(n);
//把同一连通块下的各顶点合并为同一集合
for(int i=1;i<=n;i++){
for(int j=0;j<Adj[i].size();j++){
//碰到删除结点直接跳过
if(i==delVertex||Adj[i][j]==delVertex) continue;
Union(i,Adj[i][j]);
//printf("合并%d和%d\n",i,Adj[i][j]);
}
}
for(int i=1;i<=n;i++){
isRoot[findFather(i)]=true;
}
//计算连通块数,注意要忽略删除结点
int ans=0;
for(int i=1;i<=n;i++){
if(isRoot[i]&&i!=delVertex) ans++;
}
printf("%d\n",ans-1);
}
return 0;
}
93. 旅行计划(1030)
本题要求确定两点间最短路径,最短路径相同时,取权值和最小的一条。直接用Dijstra算法,增加一个记录权重的矩阵和前驱结点的矩阵即可。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int INF=0x3fffffff;
const int maxn=510;
int n,m,s,des;
int G[maxn][maxn],d[maxn],cost[maxn][maxn],w[maxn];
bool vis[maxn]={false};
int pre[maxn];//保存前驱结点
vector<int> path;
void Dijstra(int s){
fill(d,d+maxn,INF);
fill(w,w+maxn,INF);
//for(int i)
d[s]=0;
w[s]=0;
for(int i=0;i<n;i++){
int u=-1,MIN=INF;
for(int j=0;j<n;j++){
if(vis[j]==false&&d[j]<MIN){
u=j;
MIN=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++){
if(vis[v]==false&&G[u][v]!=INF){
if(d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
w[v]=w[u]+cost[u][v];
pre[v]=u;
}
else if(d[u]+G[u][v]==d[v]&&w[u]+cost[u][v]<w[v]){
w[v]=w[u]+cost[u][v];
pre[v]=u;
}
}
}
}
}
void showPath(int d){//输入目的地,保存最短路径
while(d!=s){
path.push_back(d);
d=pre[d];
}
path.push_back(d);
for(int i=path.size()-1;i>=0;i--){
printf("%d",path[i]);
printf(" ");
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&des);
int u,v;
fill(G[0],G[0]+maxn*maxn,INF);
fill(cost[0],cost[0]+maxn*maxn,INF);
for(int i=0;i<m;i++){
scanf("%d%d",&u,&v);
scanf("%d%d",&G[u][v],&cost[u][v]);
G[v][u]=G[u][v];
cost[v][u]=cost[u][v];
}
Dijstra(s);
showPath(des);
printf("%d %d",d[des],w[des]);
return 0;
}
94. 加油站(1072)
本题要求从几个备选站点中找到一个位置,使得加油站到最近的居民区尽可能远,但又要保证所有房子都在其服务范围内。多解时取平均距离小的,再多解取系数小的。
问题:
(1)读入问题,加油站的编号是G开头的,因此需要用字符串读入,根据首字符是否是G区分;
(2)确定加油站与居民区最短距离:对每个加油站,使用Dijstra算法计算最短距离,(注意,备选加油站也可以作为中间结点),同时距离要满足要求。
#include<algorithm>
#include<math.h>
#include<cstring>
using namespace std;
const int maxn=1020;
const int INF=0x3fffffff;
int n,m,k,Ds;
double minDis=-1,minAvg=INF,dis,avg;
int ind=-1;
int G[maxn][maxn],d[maxn];
bool vis[maxn];
//判断加油站到每个居民区距离是否符合要求,若符合,计算最近距离和平均距离
bool isValid(){
double sum=0;
dis=INF;
for(int i=1;i<=n;i++){
if(d[i]>Ds) return false;
sum+=d[i];
dis=min(dis,1.0*d[i]);
}
avg = sum/n;
return true;
}
void Dijstra(int s){
//由于要多次调用,需要先进行初始化
//printf("%d调用\n",s);
for(int i=1;i<=n+m;i++){
vis[i]=false;
d[i]=INF;
}
d[s]=0;
for(int i=0;i<n+m;i++){
int u=-1,MIN=INF;
for(int j=1;j<=n+m;j++){
if(vis[j]==false&&d[j]<MIN){
u=j;
MIN=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=1;v<=n+m;v++){
if(vis[v]==false&&G[u][v]!=INF&&d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
}
}
}
if(isValid()){
if(dis>minDis){
minDis=dis;
minAvg=avg;
ind=s;
}
else if(dis==minDis&&avg<minAvg){
minAvg=avg;
ind=s;
}
else if(dis==minDis&&avg==minAvg&&s<ind){
ind=s;
}
//printf("%d调用后index=%d\n",s,index);
}
}
int getID(char str[]){
int i=0,len=strlen(str),ID=0;
while(i<len){
if(str[i]!='G'){
ID=ID*10+str[i]-'0';
}
i++;
}
if(str[0]=='G') return ID+n;
return ID;
}
int main(){
scanf("%d%d%d%d",&n,&m,&k,&Ds);
char c1[10],c2[10];
int u,v;
fill(G[0],G[0]+maxn*maxn,INF);
for(int i=0;i<k;i++){
scanf("%s%s",c1,c2);
//加油站序号转化为n~n+m,注意这里不一定只有1位数字
u=getID(c1);
v=getID(c2);
scanf("%d",&G[u][v]);
G[v][u]=G[u][v];
}
for(int i=n+1;i<=n+m;i++){
Dijstra(i);
}
if(ind==-1){
printf("No Solution");
}
else{
printf("G%d\n",ind-n);
printf("%.1f %.1f",minDis,round(minAvg*10)/10);
}
return 0;
}
语法:double类型四舍五入保留n位数,可以先乘10^n,再用round,最后再除以10^n。如四舍五入保留1位数,round(minAvg*10)/10;
95. 条条大路通罗马(1087)
本题要求选出去罗马城的最短道路,多解时选择城市权值和高的,再多解选择平均值高的
思路也很直接:Dijstra在松弛加上两个新的判断条件即可。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
const int maxn=210;
const int INF=0x3fffffff;
map<string,int> strToint;
map<int,string> intTostr;
int n,k,numCity=1;
bool vis[maxn]={false};
int G[maxn][maxn],d[maxn],happy[maxn],w[maxn],sumCity[maxn];
int pre[maxn],numPath[maxn];
void Dijstra(int s){
fill(d,d+maxn,INF);
fill(w,w+maxn,0);
fill(sumCity,sumCity+maxn,0);
d[s]=0;
w[s]=0;
sumCity[s]=0;
numPath[s]=1;
for(int i=0;i<n;i++){
int u=-1,MIN=INF;
for(int j=0;j<n;j++){
if(vis[j]==false&&d[j]<MIN){
u=j;
MIN=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++){
if(vis[v]==false&&G[u][v]!=INF){
if(d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
w[v]=w[u]+happy[v];
sumCity[v]=sumCity[u]+1;
pre[v]=u;
//这里不是把最短路径数置为1,因为只能说明发现从u到v这一条更近的路
//但是从起点出发到u可能存在多条路径,因此应该等于u的最短路径数
numPath[v]=numPath[u];
}
else if(d[u]+G[u][v]==d[v]){//存在多条路径
numPath[v]+=numPath[u];
if(w[u]+happy[v]>w[v]){//权重更大
w[v]=w[u]+happy[v];
sumCity[v]=sumCity[u]+1;
pre[v]=u;
}
else if(w[u]+happy[v]==w[v]){//权重相同,选结点少的,即平均幸福值大
if(sumCity[u]+1<sumCity[v]){
sumCity[v]=sumCity[u]+1;
pre[v]=u;
}
}
}
}
}
}
}
void showPath(int v){
if(v==0){
cout<<intTostr[v];
return;
}
showPath(pre[v]);
cout<<"->"<<intTostr[v];
}
int main(){
string st;
cin>>n>>k>>st;
strToint[st]=0;//出发点设为0
intTostr[0]=st;
string city1,city2;
for(int i=1;i<n;i++){
cin>>city1;
strToint[city1]=i;
intTostr[i]=city1;
cin>>happy[i];
}
fill(G[0],G[0]+maxn*maxn,INF);
int u,v;
for(int i=0;i<k;i++){
cin>>city1>>city2;
u=strToint[city1];
v=strToint[city2];
cin>>G[u][v];
G[v][u]=G[u][v];
}
int rom=strToint["ROM"];
Dijstra(0);
int avg=w[rom]/sumCity[rom];
cout<<numPath[rom]<<" "<<d[rom]<<" "<<w[rom]<<" "<<avg<<endl;
showPath(rom);
return 0;
}