5.19

https://www.luogu.com.cn/problem/P5960

  1. 问题转化:将每个差分约束条件$ (x_i - x_j \leq c_k) $转化为图中的一条边 \((j \rightarrow i)\),权值为 \((c_k)\)
  2. 虚拟节点:为了确保图是连通的,添加一个虚拟节点 0,并从该节点向所有其他节点连一条权值为 0 的边。
  3. SPFA 算法:使用 SPFA算法检测负环。如果某个节点的入队次数超过节点总数,则存在负环。有负环即不可差分。
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.*;

public class Main implements Runnable {

    static Scanner cin  = new Scanner(System.in);
    static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
    static int n, m;
    static final int N = (int) (5e3 + 10);
    static Vector<Verge>[] nodes = new Vector[N];
    static Queue<Integer> queue = new LinkedList<>();
    static boolean[] visited = new boolean[N];
    static int[] dist = new int[N];
    static int[] cnt = new int[N];


    public static int nextInt() {
        return cin.nextInt();
    }

    public static double nextDouble() {
        return cin.nextDouble();
    }


    public static void main(String[] args) {
        new Thread(null, new Main(), "", 1 << 20).start();
    }

    @Override
    public void run() {
        n = nextInt(); m = nextInt();
        for (int i = 0; i <= n; i++) {
            nodes[i] = new Vector<>();
        }
        for (int i = 1; i <= m; i++) {
            int to = nextInt(); int from = nextInt(); int val = nextInt();
            nodes[from].add(new Verge(to, val));
        }
        for (int i = 1; i <= n; i++) {
            nodes[0].add(new Verge(i, 0));
        }
        Arrays.fill(dist, (int) 1e9);
        if (spfa()) {
            cout.println("NO");
        } else {
            for (int i = 1; i <= n; i++) {
                cout.print(dist[i] + " ");
            }
            cout.println();
        }
        cout.flush();
    }

    public static boolean spfa() {
        queue.add(0); visited[0] = true; dist[0] = 0;
        while (!queue.isEmpty()) {
            int from = queue.remove(); visited[from] = false;
            for (Verge verge : nodes[from]) {
                int to = verge.to; int cost = verge.val;
                if (dist[to] > dist[from] + cost) {
                    dist[to] = dist[from] + cost;
                    cnt[to] = cnt[from] + 1;
                    if (cnt[to] >= n + 1) return true;
                    if (!visited[to]) {
                        queue.add(to); visited[to] = true;
                    }
                }
            }
        }
        return false;
    }



    static class Verge {
        int to;
        int val;

        Verge(int to, int val) {
            this.to = to;
            this.val = val;
        }
    }
} 

[P4568 JLOI2011] 飞行路线 - 洛谷

本题是典型的分层图最短路问题,核心是通过分层处理最多免费乘坐 k 次航线的约束。每层代表使用免费次数的状态,层间转移表示使用一次免费机会,层内转移表示正常付费。

  1. 分层图构建
    • 创建 (k+1) 层图,每层包含 n 个节点(对应原问题的 n 个城市)。
    • 第 i 层\((0 \leq i \leq k)\)表示已使用 i 次免费机会的状态。
    • 层内边:同层内的边权为航线原价(需付费)。
    • 层间边:从第 i 层的节点 u 到第 (i+1) 层的节点 v 连一条权值为 0 的边(表示使用一次免费机会,费用为 0)。
  2. Dijkstra 算法求解
    • 起点为第 0 层的起点城市 s,终点为第 0 层到第 k 层的终点城市 t 的最小值,即\(t + k * n\)
    • 使用优先队列优化的 Dijkstra 算法,在分层图中寻找从起点到各层终点的最短路径。
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;

const int INF = 1e9;
const int N = 110010;
const int M = 50010;

struct Verge {
    int to;
    int cost;
    Verge(int t, int c) : to(t), cost(c) {}
    
    // 重载小于运算符,用于优先队列的比较
    bool operator>(const Verge& other) const {
        return cost > other.cost;
    }
};

int n, m, k;
int s, t;
vector<Verge> nodes[N];
int dist[N];
bool visited[N];

int main() {
    // 读取输入
    cin >> n >> m >> k >> s >> t;
    
    // 初始化邻接表
    for (int i = 0; i <= n * (k + 1) - 1; i++) {
        nodes[i].clear();
    }
    
    // 构建图
    for (int i = 1; i <= m; i++) {
        int from, to, cost;
        cin >> from >> to >> cost;
        
        // 普通边
        nodes[from].push_back(Verge(to, cost));
        nodes[to].push_back(Verge(from, cost));
        
        // 分层图的边
        for (int j = 1; j <= k; j++) {
            // 层内边
            nodes[j * n + from].push_back(Verge(j * n + to, cost));
            nodes[j * n + to].push_back(Verge(j * n + from, cost));
            
            // 层间边(免费边)
            nodes[(j - 1) * n + from].push_back(Verge(j * n + to, 0));
            nodes[(j - 1) * n + to].push_back(Verge(j * n + from, 0));
        }
    }
    
    // Dijkstra算法
    fill(dist, dist + N, INF);
    dist[s] = 0;
    
    // 使用优先队列优化的Dijkstra
    priority_queue<Verge, vector<Verge>, greater<Verge>> queue;
    queue.push(Verge(s, 0));
    
    while (!queue.empty()) {
        Verge current = queue.top();
        queue.pop();
        
        int from = current.to;
        int cost = current.cost;
        
        // 原Java代码中的终止条件可能有误,这里注释掉
        // if ((from + 1) % n == 0) break;
        
        if (visited[from]) continue;
        visited[from] = true;
        
        for (const Verge& verge : nodes[from]) {
            int to = verge.to;
            int nowCost = verge.cost;
            
            if (visited[to]) continue;
            
            if (dist[to] > nowCost + dist[from]) {
                dist[to] = nowCost + dist[from];
                queue.push(Verge(to, dist[to]));
            }
        }
    }
    
    // 计算结果
    int ans = INF;
    for (int i = 1; i <= k + 1; i++) {
        ans = min(ans, dist[t + (i - 1) * n]);
    }
    
    cout << ans << endl;
    
    return 0;
}

[P2865 USACO06NOV] Roadblocks G - 洛谷

  1. 双向最短路径预处理
    • 使用 SPFA 算法分别计算从起点 1 到所有节点的最短距离 dist1[],以及从终点 n 到所有节点的最短距离 dist2[]
    • 这一步将原图的次短路径问题转化为:通过某条边 (u, v, w) 连接两段最短路径,即 \((dist1[u] + w + dist2[v])\)\((dist1[v] + w + dist2[u])\)
  2. 枚举边计算候选次短路径
    • 遍历所有边 (u, v, w),计算通过该边连接起点到 u、终点到 v 的最短路径长度,以及起点到 v、终点到 u 的最短路径长度,得到所有可能的候选值。
    • 这些候选值中,严格大于最短路径的最小值即为次短路径。
  3. 筛选与去重
    • 使用集合存储所有候选值,自动去重后,通过优先队列(或排序)找到严格大于最短路径的最小值。
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.*;

public class Main implements Runnable {

    static Scanner cin  = new Scanner(System.in);
    static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
    static int r, n;
    static final int N = (int) (5e3 + 10);
    static final int R = (int) (1e5 + 10);
    static Vector<Verge>[] nodes = new Vector[N];
    static int[] dist1 = new int[N];
    static int[] dist2 = new int[N];
    static boolean[] visited = new boolean[N];
    static Queue<Integer> queue = new LinkedList<>();
    static Set<Integer> ans = new HashSet<>();
    static Queue<Integer> queue2 = new PriorityQueue<>();

    public static int nextInt() {
        return cin.nextInt();
    }

    public static double nextDouble() {
        return cin.nextDouble();
    }


    public static void main(String[] args) {
        new Thread(null, new Main(), "", 1 << 20).start();
    }

    @Override
    public void run() {
        n = nextInt(); r = nextInt();
        for (int i = 1; i <= n; i++) {
            nodes[i] = new Vector<Verge>();
        }
        for (int i = 1; i <= r; i++) {
            int from = nextInt(); int to = nextInt(); int cost = nextInt();
            nodes[from].add(new Verge(to, cost));
            nodes[to].add(new Verge(from, cost));
        }
        Arrays.fill(dist1, (int) 1e9);
        spfa1();
        queue.clear();
        Arrays.fill(visited, false);
        Arrays.fill(dist2, (int) 1e9);
        spfa2();
        for (int from = 1; from <= n; from++) {
            for (Verge verge : nodes[from]) {
                int to = verge.to; int cost = verge.val;
                ans.add(dist1[from] + dist2[to] + cost);
            }
        }
        queue2.addAll(ans);
        queue2.poll();
        cout.println(queue2.poll());
        cout.flush();
    }

    public static void spfa1() {
        queue.add(1); visited[1] = true; dist1[1] = 0;
        while (!queue.isEmpty()) {
            Integer from = queue.poll(); visited[from] = false;
            for (Verge verge : nodes[from]) {
                int to = verge.to; int cost = verge.val;
                if (dist1[to] > dist1[from] + cost) {
                    dist1[to] = dist1[from] + cost;
                    if (!visited[to]) {
                        queue.add(to); visited[to] = true;
                    }
                }
            }
        }
    }
    public static void spfa2() {
        queue.add(n); visited[n] = true; dist2[n] = 0;
        while (!queue.isEmpty()) {
            Integer from = queue.poll(); visited[from] = false;
            for (Verge verge : nodes[from]) {
                int to = verge.to; int cost = verge.val;
                if (dist2[to] > dist2[from] + cost) {
                    dist2[to] = dist2[from] + cost;
                    if (!visited[to]) {
                        queue.add(to); visited[to] = true;
                    }
                }
            }
        }
    }




    static class Verge {
        int to;
        int val;

        Verge(int to, int val) {
            this.to = to;
            this.val = val;
        }
    }
}

[P2910 USACO08OPEN] Clear And Present Danger S - 洛谷

  • 纯floyd的板子题
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.*;

public class Main implements Runnable {

    static Scanner cin  = new Scanner(System.in);
    static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
    static final int N = (int) (1e2 + 10);
    static final int M = (int) (1e4 + 10);
    static int[] a = new int[M];
    static int n, m;
    static int[][] dist = new int[N][N];

    public static int nextInt() {
        return cin.nextInt();
    }

    public static double nextDouble() {
        return cin.nextDouble();
    }


    public static void main(String[] args) {
        new Thread(null, new Main(), "", 1 << 20).start();
    }

    @Override
    public void run() {
        n = nextInt(); m = nextInt();
        for (int i = 1; i <= m; i++) {
            a[i] = nextInt();
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                dist[i][j] = nextInt();
            }
        }
        floyd();
        int ans = 0;
        for (int i = 1; i < m; i++) {
            ans += dist[a[i]][a[i + 1]];
        }
        cout.println(ans);
        cout.flush();
    }

    public static void floyd() {

        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
    }


}

[P1095 NOIP 2007 普及组] 守望者的逃离 - 洛谷

  • 线性dp分类讨论,使用技能一定比不使用技能好,所以先跑一遍使用技能可能的dp值,再对不使用技能的一起dp
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.*;

public class Main implements Runnable {

    static Scanner cin  = new Scanner(System.in);
    static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
    static final int v = 17;//17m/s
    static final int SkillDistance = 60; //一次技能跑60m 消耗10点魔法
    static final int Recovery = 4;
    static int m, s, t;
    static final int T = (int) (3e5 + 10);
    static final int M = (int) (1e3 + 10);
    static int[] dp = new int[T];

    public static int nextInt() {
        return cin.nextInt();
    }

    public static double nextDouble() {
        return cin.nextDouble();
    }


    public static void main(String[] args) {
        new Thread(null, new Main(), "", 1 << 20).start();
    }

    @Override
    public void run() {
        m = cin.nextInt(); s = cin.nextInt(); t = cin.nextInt();

        for (int i = 0; i < t; i++) {
            dp[i + 1] = dp[i] + ((m >= 10) ? 1 : 0) * SkillDistance;
            int p = m;
            m -= ((p >= 10) ? 1 : 0) * 10;
            m += ((p < 10) ? 1 : 0) * Recovery;
        }

        int ptr = 0;
        for (int i = 0; i < t; i++) {
            dp[i + 1] = Math.max(dp[i + 1], dp[i] + 17);
            if (dp[i + 1] >= s) {
                ptr = i + 1;
                break;
            }
        }
        if (ptr != 0) {
            cout.println("Yes");
            cout.println(ptr);
        } else {
            cout.println("No");
            cout.println(dp[t]);
        }

        cout.flush();
    }
}

[P1077 NOIP 2012 普及组] 摆花 - 洛谷

  • \(dp[i][j]\)表示前i种花的前j盆有多少可能,为计数类dp,采用累加。因为\(dp[i][0]\)均为1种可能,需要初始化。本题采用正推。
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.*;

public class Main implements Runnable {

    static Scanner cin  = new Scanner(System.in);
    static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
    static int n, m;
    static final int MOD = 1000007;
    static final int N = (int) (1e2 + 10);
    static int[] a = new int[N];
    static int[][] dp = new int[N][N];// 前i种花的前j盆有多少种可能


    public static int nextInt() {
        return cin.nextInt();
    }

    public static double nextDouble() {
        return cin.nextDouble();
    }


    public static void main(String[] args) {
        new Thread(null, new Main(), "", 1 << 20).start();
    }

    @Override
    public void run() {
        n = nextInt(); m = nextInt();
        for (int i = 1; i <= n; i++) {
            a[i] = nextInt();
        }
        for (int i = 0; i <= n; i++) {
            dp[i][0] = 1;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= a[i]; j++) {
                for (int k = 0; k + j <= m; k++) {
                    if(j == 0 && k == 0) continue;
                    dp[i][k + j] = (dp[i - 1][k] + dp[i][k + j]) % MOD;
                }
            }
        }


        cout.println(dp[n][m] % MOD);
        cout.flush();
    }

}
posted @ 2025-05-19 12:42  Mikkeykarl  阅读(39)  评论(0)    收藏  举报