다중 표준 최적 경로에서 dijkstra 알고리즘을 사용할 때주의 할 점

문제는 pat Class A 1018에서 발생합니다. 올바른 방법은 dijkstra + DFS이지만 dijkstra 만 사용하려고합니다. dijkstra 프로세스에서는 최적 경로에 대한 두 가지 기준을 동시에 판단하고 부족과 정점 사이의 나머지 값 관계, 논리적 오류가있는 다음 코드를 작성하십시오.

#include <cstdio>
#include <climits>
#include <stack>
#include <algorithm>

using namespace std;

const int maxv = 501;
const int INF = INT_MAX;

int C, N, Sp, M;
int v_weight[maxv];
int graph[maxv][maxv]={
    
    };
int d[maxv];
bool confirmed[maxv]={
    
    };
int lack[maxv]={
    
    }; //使起点到i的最短路上的顶点全部达到perfect还差多少自行车
int remain[maxv]={
    
    }; //带回去多少自行车
int pre[maxv]; //记录路径

void init();
void dijkstra();

int main(){
    
    
    scanf("%d%d%d%d", &C, &N, &Sp, &M);
    for(int i=1; i<=N; i++) scanf("%d", &v_weight[i]);
    while(M--){
    
    
        int si, sj, w;
        scanf("%d%d%d", &si, &sj, &w);
        graph[si][sj] = graph[sj][si] = w;
    }
    init();
    dijkstra();

    printf("%d ", lack[Sp]);
    stack<int> path;
    int v = Sp;
    while(v!=0){
    
    
        path.push(v);
        v = pre[v];
    }
    path.push(0);
    int n = path.size();
    while(n--){
    
    
        printf("%d", path.top());
        path.pop();
        if(n>0) printf("->");
    }
    printf(" %d", remain[Sp]);

    return 0;
}

void init(){
    
    
    fill(d, d+N+1, INF);
    d[0] = 0;
    for(int i=0; i<=N; i++) pre[i] = i;
    return;
}

void dijkstra(){
    
    
    for(int k=0; k<=N; k++){
    
    
        int u=-1, min_d=INF;
        for(int i=0; i<=N; i++){
    
    
            if(!confirmed[i] && d[i]<min_d){
    
    
                u = i;
                min_d = d[i];
            }
        }
        if(u==-1) return;
        confirmed[u] = true;
        for(int j=0; j<=N; j++){
    
    
            if(!confirmed[j] && graph[u][j]!=0){
    
    
                if(d[u]+graph[u][j]<d[j]){
    
    
                    d[j] = d[u] + graph[u][j];
                    //相邻点的递推
                    int need = (C/2) - v_weight[j];
                    if(need>0){
    
    
                        //使j达到perfect缺车
                        if(remain[u]>=need){
    
    
                            //到u结束时,u以前的顶点全部达到perfect后,此时多余的车可以cover
                            lack[j] = lack[u];
                            remain[j] = remain[u] - need;
                        }
                        else{
    
    
                            //此时多余的车不够cover
                            lack[j] = lack[u] + need - remain[u];
                            remain[j] = 0;
                        }
                    }
                    else{
    
    
                        //使j达到perfect需要带走车
                        lack[j] = lack[u];
                        remain[j] = remain[u] + v_weight[j] - (C/2);
                    }
                    pre[j] = u;
                }
                else if(d[u]+graph[u][j]==d[j]){
    
    
                    //计算如果走这条备选路,到j处的lack和remain,然后以第二标准作比较
                    int t_lack, t_remain;
                    int need = (C/2) - v_weight[j];
                    if(need>0){
    
    
                        if(remain[u]>=need){
    
    
                            t_lack = lack[u];
                            t_remain = remain[u] - need;
                        }
                        else{
    
    
                            t_lack = lack[u] + need - remain[u];
                            t_remain = 0;
                        }
                    }
                    else{
    
    
                        t_lack = lack[u];
                        t_remain = remain[u] + v_weight[j] - (C/2);
                    }
                    if(t_lack<lack[j] || (t_lack==lack[j] && t_remain<remain[j])){
    
    
                        lack[j] = t_lack;
                        remain[j] = t_remain;
                        pre[j] = u;
                    }
                }
            }
        }
    }
    return;
}

제출 후 테스트 포인트 7은 통과 할 수 없었지만 엄격한 데이터가없는 Niuke.com은 모두 통과 할 수 있습니다.
제목에서 최적 경로의 두 가지 기준 :
① 경로 길이가 가장 작은 d [u] + graph [u] [j] <d [j]
② ①이 같을 때 부족이 가장 작거나 부족이 동일하고 나머지가 가장 작다. t_lack <lack [j] || (t_lack == lack [j] && t_remain <remain [j])
동시에 ①② 를 판단하는 오류는 표준 ①이 정점, 표준 ②에는 반복이 없습니다.
즉, 시작점 s에서 정점 k까지의 특정 경로가 최단 경로이고 s에서 다른 정점까지의 경로도 최단 경로 여야 함을 알 수 있습니다. 이것은 또한 여러 최단 경로를 기록하기 위해 사전 배열을 사용하는 원리입니다.
그러나 s에서 k 로의 특정 경로가 기준 ②에서 최적으로 만든다면 s에서 다른 꼭지점으로의 경로가 반드시 최적은 아닙니다.
나는 표준 ①을 재귀 적 최적 경로 표준이라고 부르는데, 이러한 표준에는 최소 / 대 경로 길이, 최소 / 대 에지 가중치 누적, 최소 / 대 포인트 누적이 포함됩니다.
표준 ②를 되풀이없는 최적 경로 표준으로 부르고 이러한 표준이 질문에 나타나면 먼저 dijkstra + DFS를 찾아 최단 경로를 찾은 다음 다른 표준을 기반으로 최적 경로를 판단해야합니다.
따라서이 질문에 dijkstra를 사용할 수는 없습니다.
올바른 코드 :

#include <cstdio>
#include <climits>
#include <vector>
#include <algorithm>

using namespace std;

const int maxv = 501;
const int INF = INT_MAX;

int C, N, Sp, M;
int v_weight[maxv];
int graph[maxv][maxv]={
    
    };
int d[maxv];
bool confirmed[maxv]={
    
    };
vector<int> pre[maxv]; //最短路径不唯一

int f_lack = INF, f_remain = INF; //最优值
vector<int> path, t_path;

void init();
void dijkstra();
void DFS(int v);
void check();

int main(){
    
    
    scanf("%d%d%d%d", &C, &N, &Sp, &M);
    for(int i=1; i<=N; i++) scanf("%d", &v_weight[i]);
    while(M--){
    
    
        int si, sj, w;
        scanf("%d%d%d", &si, &sj, &w);
        graph[si][sj] = graph[sj][si] = w;
    }
    init();
    dijkstra(); //先找最短路径
    DFS(Sp); //从这些最短路径中,以第二标准找到最优路径
    printf("%d ", f_lack);
    int n = path.size();
    for(int i=n-1; i>=0; i--){
    
    
        printf("%d", path[i]);
        if(i>0) printf("->");
    }
    printf(" %d", f_remain);

    return 0;
}

void init(){
    
    
    fill(d, d+N+1, INF);
    d[0] = 0;
    return;
}

void dijkstra(){
    
    
    for(int k=0; k<=N; k++){
    
    
        int u=-1, min_d=INF;
        for(int i=0; i<=N; i++){
    
    
            if(!confirmed[i] && d[i]<min_d){
    
    
                u = i;
                min_d = d[i];
            }
        }
        if(u==-1) return;
        confirmed[u] = true;
        for(int j=0; j<=N; j++){
    
    
            //u->j
            if(!confirmed[j] && graph[u][j]!=0){
    
    
                if(d[u]+graph[u][j]<d[j]){
    
    
                    d[j] = d[u] + graph[u][j];
                    pre[j].clear();
                    pre[j].push_back(u);
                }
                else if(d[u]+graph[u][j]==d[j]){
    
    
                    pre[j].push_back(u);
                }
            }
        }
    }
    return;
}

void DFS(int v){
    
    
    t_path.push_back(v); //在递归内加入路径
    if(v==0){
    
    
        check();
        return;
    }
    int n = pre[v].size();
    for(int i=0; i<n; i++){
    
    
        DFS(pre[v][i]);
        t_path.pop_back(); //在递归外紧接着从路径中删去,还原状态。
    }
    return;
}

void check(){
    
    
    int n = t_path.size();
    int lack=0, remain=0;
    for(int i=n-2; i>=0; i--){
    
    
        int v = t_path[i];
        int need = (C/2) - v_weight[v];
        if(need>0){
    
    
            //使v达到perfect缺车
            if(remain>=need){
    
    
                //上一个点的remain足够cover
                remain -= need;
            }
            else{
    
    
                //上一个点的remain不够cover
                lack = lack + need - remain;
                remain = 0;
            }
        }
        else{
    
    
            //使v达到perfect要拿走车
            remain = remain - need;
        }
    }
    if(lack<f_lack || (lack==f_lack && remain<f_remain)){
    
    
        f_lack = lack;
        f_remain = remain;
        path = t_path;
    }
    return;
}

벡터 배열 사전은 역 그래프를 구성합니다. 직접 접근하고자한다면 재귀가 돌아올 때 하나의 상태 만 기록 할 수 있고 중간 꼭지점은 여러 상태를 가질 수 있으므로 한 번의 재귀로 완료 할 수 없습니다. DFS의 재귀에서 t_path를 사용하여 경로를 기록하고 시작점에 도달 한 다음 check 함수를 호출하여 부족 및 잔류를 계산하고 path를 사용하여 최종 결과를 수신합니다
여기에 사진 설명 삽입
. 모두 단방향이므로 DFS는이 그래프의 정점을 기록하기 위해 vis 배열을 설정할 필요가 없습니다.
두 가지 DFS 아이디어 :
1. 재귀 내의 경로에 지점을 기록한 다음 재귀 바로 외부의 경로에서 지점을 삭제하여 상태를 복원합니다. 재귀에 들어가기 전에 t_path에는 현재 정점 v가없고 재귀 반환 후에 t_path에는 v가 있습니다.
2. 입력과 삭제가 모두 재귀 적으로 수행되므로 더 좋습니다. 재귀를 입력하기 전 t_path의 내용은 재귀 반환 후와 동일합니다.

추천

출처blog.csdn.net/sinat_37517996/article/details/104500274