PAT甲级刷题笔记(5)

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值