"Guía avanzada de concurso de algoritmos" # 0x61 Teoría de gráficos-Ruta más corta-Capítulo1

Enlace del título: https://www.acwing.com/activity/content/punch_the_clock/6/

Hay muchas soluciones para el camino más corto:

BFS

Solo aplicable a gráficos con un peso de borde de 1.

DP

Solo es aplicable a la solución de DAG. Cuando un punto no existe, su respuesta puede determinarse de inmediato. Este algoritmo no tiene requisitos distintos a DAG y puede ejecutar gráficos con pesos negativos.

Dijkstra

Algoritmo de ruta más corta eficiente y estable de una sola fuente, adecuado para tratar con bordes de peso no negativos.

La complejidad es: \ (O ((n + m) \ log (n + m)) \)

int n;
vector<pii> G[MAXN + 5];

bool vis[MAXN + 5];
int dis[MAXN + 5];
priority_queue<pii> PQ;

void Dijkstra(int s) {    
    while(!PQ.empty())
        PQ.pop();
    memset(vis, 0, sizeof(vis[0]) * (n + 1));
    memset(dis, INF, sizeof(dis[0]) * (n + 1));
    dis[s] = 0;
    PQ.push({-dis[s], s});
    while(!PQ.empty()) {
        int u = PQ.top().second;
        PQ.pop();
        if(vis[u])
            continue;
        vis[u] = 1;
        for(auto &e : G[u]) {
            int v = e.first, w = e.second;
            if(dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                PQ.push({-dis[v], v});
            }
        }
    }
    return;
}

Las técnicas comunes son:

Dibujo inverso: el punto de origen es el clic completo, pero el punto de hundimiento es solo uno y el borde puede invertirse en este momento.

Construcción jerárquica: especifique ciertos bordes para usar algunas propiedades especiales, como algo de magia que se puede usar para reducir el peso del borde, pero el poder mágico es limitado. El problema es que el rango de poder mágico generalmente no es demasiado grande. Simplemente sucede que cada punto de la imagen original se puede dividir en el rango de poder mágico. Preste atención para extraer la naturaleza común del borde para reducir el almacenamiento del borde.

Múltiples puntos se usan como puntos fuente al mismo tiempo : es diferente de la ruta más corta de múltiples fuentes. Aquí, para encontrar la ruta más corta desde el punto fuente establecido a cada punto, puede construir un punto súper fuente y luego conectar el punto fuente establecido al borde con un peso de 0, o directamente Lanza todo el conjunto de puntos en PQ.

Sumidero único de fuente única: similar a BFS bidireccional, será más rápido al salir. O cuando se empuja el punto de encuentro fuera del PQ presionando, la salida también se puede acelerar.

BFS de doble extremo

También conocido como 0-1BFS, el uso de colas de doble extremo en lugar de la cola BFS es adecuado para gráficos con pesos de borde de solo 0 y 1, y también puede entenderse como el algoritmo Dijkstra para controlar manualmente las colas de prioridad (de hecho, no hay diferencia entre estos varios).

La complejidad es: \ (O (n + m) \)

vector<pii> G[MAXN + 5];

int dis[MAXN + 5];

deque<int> DQ;
void BFS(int s, int Limit) {
    DQ.clear();
    memset(dis, INF, sizeof(dis[0]) * (n + 1));
    dis[s] = 0;
    DQ.push_back(s);
    while(!DQ.empty()) {
        int u = DQ.front();
        DQ.pop_front();
        if(u == n)
            break;
        for(auto &e : G[u]) {
            int v = e.first, w = e.second;
            if(w == 0) {
                if(dis[u] < dis[v]) {
                    dis[v] = dis[u];
                    DQ.push_front(v);
                }
            } else {
                if(dis[u] + 1 < dis[v]) {
                    dis[v] = dis[u] + 1;
                    DQ.push_back(v);
                }
            }
        }
    }
    return;
}

Consejos:

Es posible convertir algunos problemas a 0-1BFS para resolver, por ejemplo, el siguiente 340 es encontrar el valor máximo + construcción en capas, porque el valor máximo puede ser dicotomizado, y luego de acuerdo con la relación entre el valor máximo para distinguir si el peso del borde es 0 o 1 Al mismo tiempo, la construcción en capas es el proceso de BFS.

SPFA

También conocido como "algoritmo de Bellman-Ford para la optimización de colas", la complejidad es muy falsa y lo peor puede degradarse a \ (O (nm) \) . Es aplicable a cualquier gráfico. Si no hay un anillo negativo que pueda alcanzarse desde el punto fuente, SPFA puede calcular la ruta más corta de una sola fuente, de lo contrario informará la existencia de un anillo negativo después de que el mismo nodo se ponga en cola más de n veces.

Floyd

La implementación es muy simple: la ruta más corta entre dos puntos también puede detectar correctamente el bucle negativo. (Después de que Floyd termine, hay un punto en el que el camino más corto de uno a uno es un número negativo, por lo que se puede hacer un círculo indefinidamente) La desventaja es que la complejidad es demasiado grande. Solo apto para fotos muy pequeñas.

340. Línea de comunicación.

Tema: Hay N estaciones base de comunicación en los suburbios, P cables bidireccionales y el cable i-ésimo conecta las estaciones base Ai y Bi. En particular, la estación base No. 1 es la estación principal de la compañía de comunicación, y la estación base No. N está ubicada en una granja. Ahora, el agricultor quiere actualizar la línea de comunicación, en la que se necesita un cable Li para actualizar el i-ésimo cable. La compañía telefónica tiene descuentos. El agricultor puede designar una ruta desde la estación base 1 a la estación base N, y especificar no más de K cables a lo largo de la ruta, y la compañía telefónica proporcionará un servicio de actualización gratuito. El agricultor solo necesita pagar el costo de actualizar el cable más caro entre los cables restantes en el camino. Solicite al menos cuánto dinero para completar la actualización.

Rango de datos:

0≤K <N≤1000,
1≤P≤10000,
1≤Li≤1000000

Solución:

Dijkstra jerárquico

De especial a general, primero considere el caso de K = 0. En este momento, es encontrar el camino más corto, pero no es una operación de suma sino una operación máxima, que puede ser resuelta directamente por el algoritmo Dijkstra. Tenga en cuenta que parece que he hecho este tipo de preguntas antes (parece que la primera vez que lo vi en 2018) se considera desde el diagrama en capas. Si observa el rango de datos, puede crear un gráfico en capas.

Divida un punto en K + 1 puntos, el binario (id, k) indica que el punto con id se ha usado k veces libre, luego el borde original también se divide en bordes súper múltiples, pero es muy fácil prestar atención a Hasta estos bordes pueden estar muy comprimidos.

La complejidad es \ (O (n * k * \ log (n * k)) \) .

escritura de vectores:

const int MAXNK = 1000 * 1001;

int n, p, k;
vector<pii> G[MAXNK + 5];

int id_ki_pos(int id, int ki) {
    return (id - 1) * (k + 1) + ki + 1;
}

int pos_id(int pos) {
    return (pos - 1) / (k + 1) + 1;
}

int pos_ki(int pos) {
    return (pos - 1) % (k + 1);
}

bool vis[MAXNK + 5];
int dis[MAXNK + 5];
priority_queue<pii> PQ;

void Dijkstra(int sid) {
    while(!PQ.empty())
        PQ.pop();
    memset(vis, 0, sizeof(vis[0]) * (n + 1) * (k + 1));
    memset(dis, INF, sizeof(dis[0]) * (n + 1) * (k + 1));
    int spos = id_ki_pos(sid, 0);
    dis[spos] = 0;
    PQ.push({-dis[spos], spos});
    while(!PQ.empty()) {
        int upos = PQ.top().second;
        PQ.pop();
        if(vis[upos])
            continue;
        vis[upos] = 1;
        int uid = pos_id(upos);
        int uki = pos_ki(upos);
        if(uid == n)
            break;
        for(auto &e : G[uid]) {
            int vid = e.first, w = e.second;
            {
                int v0pos = id_ki_pos(vid, uki);
                if(max(dis[upos], w) < dis[v0pos]) {
                    dis[v0pos] = max(dis[upos], w);
                    PQ.push({-dis[v0pos], v0pos});
                }
            }
            if(uki < k) {
                int v1pos = id_ki_pos(vid, uki + 1);
                if(max(dis[upos], 0) < dis[v1pos]) {
                    dis[v1pos] = max(dis[upos], 0);
                    PQ.push({-dis[v1pos], v1pos});
                }
            }
        }
    }
    return;
}

void TestCase() {
    scanf("%d%d%d", &n, &p, &k);
    for(int i = 1; i <= n; ++i)
        G[i].clear();
    for(int i = 1; i <= p; ++i) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        G[u].push_back({v, w});
        G[v].push_back({u, w});
    }
    Dijkstra(1);
    int ans = INF;
    for(int ki = 0; ki <= k; ++ki)
        ans = min(ans, dis[id_ki_pos(n, ki)]);
    if(ans == INF)
        ans = -1;
    printf("%d\n", ans);
    return;
}

Escritura estrella encadenada hacia adelante:

const int MAXNK = 1000 * 1001;
const int MAXP = 1000000;

int n, p, k;

int G[MAXNK + 5];
struct Edge {
    int v, w, nxt;
    Edge() {}
    Edge(int v, int w, int nxt): v(v), w(w), nxt(nxt) {}
} edge[MAXP * 2 + 5];
int top;

void Init() {
    top = 0;
    memset(G, -1, sizeof(G[0]) * (n + 1));
}

void AddEdge(int u, int v, int w) {
    ++top;
    edge[top] = Edge(v, w, G[u]);
    G[u] = top;
}

int id_ki_pos(int id, int ki) {
    return (id - 1) * (k + 1) + ki + 1;
}

int pos_id(int pos) {
    return (pos - 1) / (k + 1) + 1;
}

int pos_ki(int pos) {
    return (pos - 1) % (k + 1);
}

bool vis[MAXNK + 5];
int dis[MAXNK + 5];
priority_queue<pii> PQ;

void Dijkstra(int sid) {
    while(!PQ.empty())
        PQ.pop();
    memset(vis, 0, sizeof(vis[0]) * (n + 1) * (k + 1));
    memset(dis, INF, sizeof(dis[0]) * (n + 1) * (k + 1));
    int spos = id_ki_pos(sid, 0);
    dis[spos] = 0;
    PQ.push({-dis[spos], spos});
    while(!PQ.empty()) {
        int upos = PQ.top().second;
        PQ.pop();
        if(vis[upos])
            continue;
        vis[upos] = 1;
        int uid = pos_id(upos);
        int uki = pos_ki(upos);
        if(uid == n)
            break;
        for(int eid = G[uid]; eid != -1; eid = edge[eid].nxt) {
            int vid = edge[eid].v, w = edge[eid].w;
            {
                int v0pos = id_ki_pos(vid, uki);
                if(max(dis[upos], w) < dis[v0pos]) {
                    dis[v0pos] = max(dis[upos], w);
                    PQ.push({-dis[v0pos], v0pos});
                }
            }
            if(uki < k) {
                int v1pos = id_ki_pos(vid, uki + 1);
                if(max(dis[upos], 0) < dis[v1pos]) {
                    dis[v1pos] = max(dis[upos], 0);
                    PQ.push({-dis[v1pos], v1pos});
                }
            }
        }
    }
    return;
}

void TestCase() {
    scanf("%d%d%d", &n, &p, &k);
    Init();
    for(int i = 1; i <= p; ++i) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        AddEdge(u, v, w);
        AddEdge(v, u, w);
    }
    Dijkstra(1);
    int ans = INF;
    for(int ki = 0; ki <= k; ++ki)
        ans = min(ans, dis[id_ki_pos(n, ki)]);
    if(ans == INF)
        ans = -1;
    printf("%d\n", ans);
    return;
}

Cosecha:

  1. Estos subíndices son más molestos, debe dibujarlos claramente en el papel.
  2. El número de bordes es relativamente grande y el número de capas también es relativamente grande. Es fácil obtener espacio cuando se agregan todos los bordes. Veo que la escritura de todos los bordes es simplemente una tontería.
  3. La velocidad de la estrella hacia adelante encadenada es significativamente más rápida que el vector, aproximadamente 6 veces más rápido, lo más probable es que la optimización de O2 no esté activada.

Dos puntos + BFS de doble punta

El problema es minimizar el valor máximo, que es un problema común de dicotomía + verificación. Si la dicotomía enumera un valor máximo maxw, solo necesita registrar todos los bordes <= maxw como costo = 0 (no utilice tiempos libres), Y registre el lado de> maxw como cost = 1 (use 1 veces gratis), que se convierte en un problema clásico de 0-1BFS, que se resuelve con una cola de doble extremo. Cost = 1 se agrega desde la cola del equipo, y cost = 0 se toma de la cabeza del equipo. Únete

Está demostrado que este algoritmo es el más rápido y ahorra mucho espacio, porque la constante BFS de doble extremo es extremadamente pequeña y la velocidad de verificación es extremadamente rápida (velocidad de verificación \ (O (n) \) ).

La complejidad es \ (O (n * \ log MAXL) \) .

const int MAXN = 1000;
const int MAXP = 1000000;

int n, p, k;

int G[MAXN + 5];
struct Edge {
    int v, w, nxt;
    Edge() {}
    Edge(int v, int w, int nxt): v(v), w(w), nxt(nxt) {}
} edge[MAXP * 2 + 5];
int top;

void Init() {
    top = 0;
    memset(G, -1, sizeof(G[0]) * (n + 1));
}

void AddEdge(int u, int v, int w) {
    ++top;
    edge[top] = Edge(v, w, G[u]);
    G[u] = top;
}

int dis[MAXN + 5];

deque<int> DQ;
bool BFS(int s, int Limit) {
    DQ.clear();
    memset(dis, INF, sizeof(dis[0]) * (n + 1));
    dis[s] = 0;
    DQ.push_back(s);
    while(!DQ.empty()) {
        int u = DQ.front();
        DQ.pop_front();
        if(u == n)
            break;
        for(int eid = G[u]; eid != -1; eid = edge[eid].nxt) {
            int v = edge[eid].v, w = edge[eid].w;
            if(w <= Limit) {
                if(dis[u] < dis[v]) {
                    dis[v] = dis[u];
                    DQ.push_front(v);
                }
            } else {
                if(dis[u] + 1 < dis[v]) {
                    dis[v] = dis[u] + 1;
                    DQ.push_back(v);
                }
            }
        }
    }
    return dis[n] <= k;
}

void TestCase() {
    scanf("%d%d%d", &n, &p, &k);
    Init();
    for(int i = 1; i <= p; ++i) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        AddEdge(u, v, w);
        AddEdge(v, u, w);
    }
    int L = 0, R = INF, ans;
    while(1) {
        int M = (L + R) >> 1;
        if(L == M) {
            if(BFS(1, L)) {
                ans = L;
                break;
            }
            ans = R;
            break;
        }
        if(BFS(1, M))
            R = M;
        else
            L = M + 1;
    }
    if(ans == INF)
        ans = -1;
    printf("%d\n", ans);
    return;
}

Cosecha:

  1. El problema de minimizar el valor máximo se puede convertir en enumeración binaria + verificación rápida.
  2. No se equivoque sobre el rango de valores dicotómicos, aquí comienza desde 0.
  3. La R inicial es infinito en la dicotomía, y alcanzar el infinito en la dicotomía indica que no hay solución.
  4. BFS no requiere una matriz visual.

341. Mejor comercio

Título: C tiene n grandes ciudades ym carreteras, cada carretera se conecta a dos de estas n ciudades. Hay como máximo una carretera directamente conectada entre dos ciudades. Algunas de las carreteras m son carreteras de un solo sentido, y algunas son carreteras de dos vías. Las carreteras de dos vías se cuentan como una cuando se cuenta el número de carreteras. El país C tiene un territorio vasto y la distribución de recursos varía de un lugar a otro, lo que da como resultado que el precio del mismo producto en diferentes ciudades no sea necesariamente el mismo. Sin embargo, el precio de compra y el precio de venta de la misma mercancía en la misma ciudad son siempre los mismos. Merchant Along vino a viajar al país C. Cuando se enteró del mensaje "el precio del mismo producto puede ser diferente en diferentes ciudades", decidió utilizar la diferencia entre los productos en diferentes ciudades para ganar un poco de gastos de viaje mientras viaja. Supongamos que las etiquetas de las n ciudades en el país C son de 1 a n, Along decidió comenzar desde la ciudad 1 y finalmente terminó su viaje en la ciudad n. En el proceso de turismo, cualquier ciudad puede repetirse muchas veces, pero no es necesario que pase por todas las n ciudades. Además, gana los gastos de viaje a través de este método comercial: elegirá una ciudad que pase para comprar su mercancía favorita, una bola de cristal, y luego venderá la bola de cristal en otra ciudad después de pasar, utilizando la diferencia obtenida como Gastos de viaje. Debido a que Along viajó principalmente al país C, decidió que este comercio solo se llevaría a cabo como máximo una vez. Por supuesto, no necesitaría comerciar si no podía hacer la diferencia. Ahora proporcione los precios de la bola de cristal de n ciudades y la información de m carreteras (los números de las dos ciudades conectadas a cada carretera y el tráfico de la carretera).

Dígale a Aaron cuánto viaje puede ganar.

Rango de datos:

1≤n≤100000, 1≤m≤500000,
1≤Precio de
bola de cristal en varias ciudades≤100

Solución: Debido a que cada ciudad puede pasar repetidamente, el problema se convierte en esto: comenzar desde la ciudad 1, llegar a la ciudad x, comprar una bola de cristal, comenzar desde la ciudad x, llegar a la ciudad y, vender una bola de cristal; Salga de la ciudad y y llegue a la ciudad n. Mirando los datos, ¿el precio de la bola de cristal parece ser un gran avance?

Pseudo-algoritmo: este problema tiene una solución muy obvia para componentes conectados fuertes. Primero, reduzca todos los componentes conectados fuertes, registre el valor máximo y mínimo de cada nuevo punto, y luego conviértalo en un DAG. Luego encuentre el valor mínimo (Dijkstra) en la ruta al otro punto desde el punto 1 en la nueva figura que contiene la figura original, y luego encuentre el otro punto al punto de encuentro desde el punto n que contiene la figura original en la nueva figura. El valor máximo en la ruta (Dijkstra en el gráfico inverso), y luego tome el valor máximo para la diferencia transversal.

const int MAXN = 1e5;

int n, m;
int price[MAXN + 5];

namespace SCC {
    int n;
    vector<int> G[MAXN + 5], BG[MAXN + 5];

    int c1[MAXN + 5], cntc1;
    int c2[MAXN + 5], cntc2;
    int s[MAXN + 5], cnts;

    int n2;
    vector<int> V2[MAXN + 5];
    vector<int> G2[MAXN + 5], BG2[MAXN + 5];

    void Init(int _n) {
        n = _n;
        cntc1 = 0, cntc2 = 0, cnts = 0;
        for(int i = 1; i <= n; ++i) {
            G[i].clear();
            BG[i].clear();
            c1[i] = 0;
            c2[i] = 0;
            s[i] = 0;
            V2[i].clear();
            G2[i].clear();
            BG2[i].clear();
        }
        return;
    }

    void AddEdge(int u, int v) {
        G[u].push_back(v);
        BG[v].push_back(u);
        return;
    }

    void dfs1(int u) {
        c1[u] = cntc1;
        for(auto &v : G[u]) {
            if(!c1[v])
                dfs1(v);
        }
        s[++cnts] = u;
    }

    void dfs2(int u) {
        V2[cntc2].push_back(u);
        c2[u] = cntc2;
        for(auto &v : BG[u]) {
            if(!c2[v])
                dfs2(v);
        }
        return;
    }

    void Kosaraju() {
        for(int i = 1; i <= n; ++i) {
            if(!c1[i]) {
                ++cntc1;
                dfs1(i);
            }
        }
        for(int i = n; i >= 1; --i) {
            if(!c2[s[i]]) {
                ++cntc2;
                dfs2(s[i]);
            }
        }
        return;
    }

    void Build() {
        n2 = cntc2;
        for(int i = 1; i <= n2; ++i) {
            for(auto &u : V2[i]) {
                for(auto &v : G[u]) {
                    if(c2[v] != i) {
                        G2[i].push_back(c2[v]);
                        BG2[c2[v]].push_back(i);
                    }
                }
            }
        }
        for(int i = 1; i <= n2; ++i) {
            sort(G2[i].begin(), G2[i].end());
            G2[i].erase(unique(G2[i].begin(), G2[i].end()), G2[i].end());
            sort(BG2[i].begin(), BG2[i].end());
            BG2[i].erase(unique(BG2[i].begin(), BG2[i].end()), BG2[i].end());
        }
        return;
    }

    int minV2[MAXN + 5], maxV2[MAXN + 5];

    bool vis[MAXN + 5];
    priority_queue<pii> PQ;

    int mindis[MAXN + 5];

    void DijkstraMin(int s) {
        while(!PQ.empty())
            PQ.pop();
        memset(vis, 0, sizeof(vis[0]) * (n2 + 1));
        memset(mindis, INF, sizeof(mindis[0]) * (n2 + 1));
        mindis[s] = minV2[s];
        PQ.push({-mindis[s], s});
        while(!PQ.empty()) {
            int u = PQ.top().second;
            PQ.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            for(auto &v : G2[u]) {
                if(mindis[u] < mindis[v]) {
                    mindis[v] = min(minV2[v], mindis[u]);
                    PQ.push({-mindis[v], v});
                }
            }
        }
        return;
    }

    int maxdis[MAXN + 5];

    void DijkstraMax(int s) {
        while(!PQ.empty())
            PQ.pop();
        memset(vis, 0, sizeof(vis[0]) * (n2 + 1));
        memset(maxdis, -INF, sizeof(maxdis[0]) * (n2 + 1));
        maxdis[s] = maxV2[s];
        PQ.push({maxdis[s], s});
        while(!PQ.empty()) {
            int u = PQ.top().second;
            PQ.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            for(auto &v : BG2[u]) {
                if(maxdis[u] > maxdis[v]) {
                    maxdis[v] = max(maxV2[v], maxdis[u]);
                    PQ.push({maxdis[v], v});
                }
            }
        }
        return;
    }

    void Solve() {
        for(int i = 1; i <= n2; ++i) {
            minV2[i] = INF, maxV2[i] = -INF;
            for(auto &u : V2[i]) {
                minV2[i] = min(minV2[i], price[u]);
                maxV2[i] = max(maxV2[i], price[u]);
            }
        }
        DijkstraMin(c2[1]);
        DijkstraMax(c2[n]);
        int ans = 0;
        for(int i = 1; i <= n2; ++i)
            ans = max(ans, maxdis[i] - mindis[i]);
        printf("%d\n", ans);
        return;
    }
}

void TestCase() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &price[i]);
    SCC::Init(n);
    for(int i = 1; i <= m; ++i) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        SCC::AddEdge(u, v);
        if(w == 2)
            SCC::AddEdge(v, u);
    }
    SCC::Kosaraju();
    SCC::Build();
    SCC::Solve();
    return;
}

Tenga en cuenta que el peso de punto Dijkstra también debe inicializarse hasta el infinito (en lugar del peso de punto correspondiente), y luego en el nodo visual (ya sea al ingresar PQ o al salir de PQ, y la actualización teórica al ingresar PQ será más rápida. ) Solo cuando se actualiza el peso en puntos correspondiente. Porque el significado de dis [u] es "el valor mínimo de los pesos de puntos de todos los puntos que pasan del punto s al punto u".

¿El método anterior será falso? Parece ser bastante falso ¿El grupo de amigos también dijo que DAG tenía problemas para ejecutar Dijkstra? Pensé en un contraejemplo:

5 5
5 4 2 1 3
1 2 1
1 3 1
2 4 1
4 3 1
3 5 1

Esta es exactamente la misma imagen después del punto de contracción, el proceso de Dijkstra es así:

Tome el punto 1, agregue {2, 3} y {4, 2} a PQ;
saque el punto 3, agregue {2, 5} a PQ;
saque el punto 5, actualice el valor mínimo del error a 2 . (Debido a que 1-> 2-> 4-> 5 es el valor mínimo 1, el valor mínimo se obtiene en el punto 4).

Pero aunque Dijkstra se quedó sin resultados incorrectos, mi algoritmo falso es demasiado robusto.

Construya un gráfico como este:

La herramienta para generar gráficos atractivos es: https://csacademy.com/app/graph_editor/

En esta figura, calculando el precio mínimo a partir de 40, Dijkstra marcará directamente 50 como 40 en lugar de los 10 correctos. A su vez, calcular el precio máximo para alcanzar 50 marcará 10 como 20 en lugar de los 90 correctos. La razón es que Dijkstra es engañada por la situación óptima local.

Los datos correspondientes son:

7 8
40 100 10 20 90 15 50
1 2
1 7
2 3
3 4
3 5
4 7
5 6
6 7

Pero todavía parece estar atascado, y este algoritmo falso es demasiado robusto, ¡porque incluso actualizará los nodos que se han visitado! En otras palabras, ¿Dijkstra sería más robusto para eliminar el nodo visual omitido? Pero este algoritmo es realmente incorrecto, solo necesita asegurarse de que el nodo sucesor haya sido visitado antes de que la respuesta correcta actualice su nodo sucesor, entonces la nueva información del nodo sucesor no continuará pasando a su nodo sucesor.

Sin embargo, DP no está equivocado después del punto de contracción: cuando un punto no tiene entrada, es imposible para él modificarlo y su respuesta también está determinada.

const int MAXN = 1e5;

int n, m;
int price[MAXN + 5];

namespace SCC {
    int n;
    vector<int> G[MAXN + 5], BG[MAXN + 5];

    int c1[MAXN + 5], cntc1;
    int c2[MAXN + 5], cntc2;
    int s[MAXN + 5], cnts;

    int n2;
    vector<int> V2[MAXN + 5];
    vector<int> G2[MAXN + 5], BG2[MAXN + 5];

    void Init(int _n) {
        n = _n;
        cntc1 = 0, cntc2 = 0, cnts = 0;
        for(int i = 1; i <= n; ++i) {
            G[i].clear();
            BG[i].clear();
            c1[i] = 0;
            c2[i] = 0;
            s[i] = 0;
            V2[i].clear();
            G2[i].clear();
            BG2[i].clear();
        }
        return;
    }

    void AddEdge(int u, int v) {
        G[u].push_back(v);
        BG[v].push_back(u);
        return;
    }

    void dfs1(int u) {
        c1[u] = cntc1;
        for(auto &v : G[u]) {
            if(!c1[v])
                dfs1(v);
        }
        s[++cnts] = u;
    }

    void dfs2(int u) {
        V2[cntc2].push_back(u);
        c2[u] = cntc2;
        for(auto &v : BG[u]) {
            if(!c2[v])
                dfs2(v);
        }
        return;
    }

    void Kosaraju() {
        for(int i = 1; i <= n; ++i) {
            if(!c1[i]) {
                ++cntc1;
                dfs1(i);
            }
        }
        for(int i = n; i >= 1; --i) {
            if(!c2[s[i]]) {
                ++cntc2;
                dfs2(s[i]);
            }
        }
        return;
    }

    void Build() {
        n2 = cntc2;
        for(int i = 1; i <= n2; ++i) {
            for(auto &u : V2[i]) {
                for(auto &v : G[u]) {
                    if(c2[v] != i) {
                        G2[i].push_back(c2[v]);
                        BG2[c2[v]].push_back(i);
                    }
                }
            }
        }
        for(int i = 1; i <= n2; ++i) {
            sort(G2[i].begin(), G2[i].end());
            G2[i].erase(unique(G2[i].begin(), G2[i].end()), G2[i].end());
            sort(BG2[i].begin(), BG2[i].end());
            BG2[i].erase(unique(BG2[i].begin(), BG2[i].end()), BG2[i].end());
        }
        return;
    }

    int minV2[MAXN + 5], maxV2[MAXN + 5];
    int indeg[MAXN + 5], outdeg[MAXN + 5];
    int vis1[MAXN + 5], visn[MAXN + 5];
    queue<int> Q;

    void DPin() {
        while(!Q.empty())
            Q.pop();
        for(int i = 1; i <= n2; ++i) {
            vis1[i] = 0;
            indeg[i] = BG2[i].size();
            if(indeg[i] == 0)
                Q.push(i);
        }
        while(!Q.empty()) {
            int u = Q.front();
            Q.pop();
            if(c2[1] == u)
                vis1[u] = 1;
            for(auto &v : G2[u]) {
                --indeg[v];
                if(indeg[v] == 0)
                    Q.push(v);
                if(vis1[u] == 1) {
                    minV2[v] = min(minV2[v], minV2[u]);
                    vis1[v] = 1;
                }
            }
        }
    }

    void DPout() {
        while(!Q.empty())
            Q.pop();
        for(int i = 1; i <= n2; ++i) {
            visn[i] = 0;
            outdeg[i] = G2[i].size();
            if(outdeg[i] == 0)
                Q.push(i);
        }
        while(!Q.empty()) {
            int u = Q.front();
            Q.pop();
            if(c2[n] == u)
                visn[u] = 1;
            for(auto &v : BG2[u]) {
                --outdeg[v];
                if(outdeg[v] == 0)
                    Q.push(v);
                if(visn[u] == 1) {
                    maxV2[v] = max(maxV2[v], maxV2[u]);
                    visn[v] = 1;
                }
            }
        }
    }

    void Solve() {
        for(int i = 1; i <= n2; ++i) {
            minV2[i] = INF, maxV2[i] = -INF;
            for(auto &u : V2[i]) {
                minV2[i] = min(minV2[i], price[u]);
                maxV2[i] = max(maxV2[i], price[u]);
            }
            indeg[i] = BG2[i].size();
            outdeg[i] = G2[i].size();
        }
        DPin();
        DPout();
        int ans = 0;
        for(int i = 1; i <= n2; ++i) {
            if(vis1[i] == 1 && visn[i] == 1)
                ans = max(ans, maxV2[i] - minV2[i]);
        }
        printf("%d\n", ans);
        return;
    }
}

void TestCase() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &price[i]);
    SCC::Init(n);
    for(int i = 1; i <= m; ++i) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        SCC::AddEdge(u, v);
        if(w == 2)
            SCC::AddEdge(v, u);
    }
    SCC::Kosaraju();
    SCC::Build();
    SCC::Solve();
    return;
}

O puede usar SPFA directamente dos veces, después de todo, este algoritmo falso es simple y sin cerebro.


Cosecha:

  1. El algoritmo falso incluso puede pasar la pregunta, si no, puede probar una ola falsa, si no está en contra de mi tarjeta de algoritmo falso, es muy difícil quedarse atascado.
  2. No hay tarjeta SPFA intencionalmente, y SPFA no será aceptado hasta la competencia regional oficial.

Supongo que te gusta

Origin www.cnblogs.com/KisekiPurin2019/p/12667294.html
Recomendado
Clasificación