문제는 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의 내용은 재귀 반환 후와 동일합니다.