1. 程式人生 > 實用技巧 >1015. Smallest Integer Divisible by K (M)

1015. Smallest Integer Divisible by K (M)

差分約束問題是求形如\(x_i \leq x_j + c_k\)的一組不等式的解,且求解的方式是轉化為圖論中的求單源最短路。

轉化為求單源最短路

  • 不等式 \(x_i \leq x_j + c_k\) 和圖中一條從\(x_i\)\(x_j\)長度為\(c_k\)的邊相對應;
  • 若在圖中求單源最短路,則有dist[i] <= dist[j] + k
  • 因此,可以將求不等式組的可行解轉換成在對應圖中求單源最短路

Part1.求不等式組的可行解

  • 源點需要滿足的條件:從源點出發,一定可以走到所有的邊(否則所求結果並未滿足全部的約束條件)

步驟

  1. 先將每一個不等式\(x_i \leq x_j + c_k\)
    ,轉化成一條從\(x_j\)走到\(x_i\),長度為\(c_k\)的一條邊
  2. 找一個超級源點,使得該源點一定可以遍歷所有邊
  3. 從源點求一遍單源最短路

負環

如果存在負環,則原不等式組一定無解,反之dist陣列會存放原不等式組的一組解

證明:

假設\(x_1,x_2,x_3,...,x_k\)構成負環,則可推知 \(x_1 \leq x_k+c_k \leq x_{k-1}+c_{k-1}+c_k \leq...\leq x_1+c_1+...+c_k\);因為是負環,所以\(c_1+...+c_k\leq0\),所以存在負環一定無解;反之,不等式組無解,對應圖中存在負環。


Part2.如何求最大值或者最小值(每一個變數\(x_i\)
的最值)

此類差分約束問題的特點

\(x_i \leq x_j + c_k\) 表示\(x_i\)\(x_j\)存在相對關係,\(x_i \leq c\)表示\(x_i\)的絕對關係;如果不等式組表述的都是相對關係,則不等式組要麼無解,如果有解則必有無窮多解;所以在要求最大值/最小值時,題目必然會給出一個絕對條件

絕對條件\(x_i \leq c\)的處理方式

建立一個超級源點\(0\) ,建立一個\(0\)指向\(x_i\)且長度為\(c\)的邊

結論

如果求最小值,則應該求最長路;如果求的是最大值,則應該求最短路

證明:

以求\(x_i\)的最大值為例:

  1. 首先,從源點\(0\)\(x_i\)
    的一條路徑(如\(x_0,x_1,x_3,...,x_{i-1},x_i\))可表述為不等式鏈\(x_i \leq x_{i-1}+c{i-1}\leq x_1+c_1+...+c_{i-1}\leq c_0+c_1+...+c_{i-1}\)
  2. \(x_i \leq c_0+c_1+...+c_{i-1}\),也就是這條路徑的長度
  3. 且需要滿足所有的不等式鏈(路徑),則應該求出所有路徑中的最小值(\(x_i\)應該小於所有路徑長度中的最小值),也即應該求最短路。最終求出的dist[i]就是\(x_i\)可以取到的最大值

例題-糖果

題目連結

題解

  • 題目要求最小值,則應該求最長路
  • 根據題意,可得出不等式組:
//求最長路,則關係式應使用 >= 描述;例如A >= B+1,表示 dist[A] >= dist[B] + 1
x = 1   A >= B 且 B >= A
x = 2   B >= A + 1
x = 3   A >= B
x = 4   A >= B + 1
x = 5   B >= A
以及隱含條件“每個小朋友都能夠分到糖果”:
假設一個超級源點X0,則任意節點滿足: X >= X0 + 1
  • 檢測:從源點0出發可以走到所有的點,則必然能夠走到所有邊;源點0滿足條件
  • 使用spfa演算法求最長路,同時檢測是否存在正環。如果存在正環,表示無解;反之所有dist[1~n]的和即為糖果的最小值

程式碼

#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100010 , M = 300010;
int e[M] , ne[M] , w[M] , h[N] , idx;
LL dist[N];
int stack[N] , cnt[N];
bool st[N];
int n,m;

void add(int l, int r, int d)
{
    e[idx] = r , w[idx] = d,  ne[idx] = h[l],  h[l] = idx++;
}

bool spfa()
{
    memset(dist , -0x3f , sizeof dist);
    int hh = 0, tt = 0;
    stack[0] = 0;
    dist[0] = 0;
    
    while(hh <= tt)
    {
        int u = stack[tt --];
        st[u] = false;
        
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if(dist[v] < dist[u] + w[i])
            {
                dist[v] = dist[u] + w[i];
                cnt[v] = cnt[u] + 1;
                if(cnt[v] >= n + 1) return false;
                if(!st[v]) st[v] = true , stack[++ tt] = v;
            }
        }
    }
    return true;
}

int main()
{
    cin >> n >> m;
    memset(h , -1 , sizeof h);
    
    for(int i = 1; i <= n; i++)  add(0 , i , 1);
    for(int i  = 0; i < m; i++)
    {
        int x , a , b;
        cin >> x >> a >> b;
        if(x == 1) add(a , b , 0) , add(b , a , 0);
        else if(x == 2) add(a , b , 1);
        else if(x == 3) add(b , a , 0);
        else if(x == 4) add(b , a,  1);
        else add(a , b , 0);
    }
    
    if(!spfa()) puts("-1");
    else 
    {
        LL ans = 0;
        for(int i = 1; i <= n; i++) 
            ans += dist[i];
        cout << ans << endl;
    }
    return 0;
}

注意點

一般來說,spfa演算法使用佇列,可以減少更新次數,從而減少執行時間;但是僅就檢測負環問題,使用棧來代替佇列會執行得更快,因為棧先進先出得性質能更快的在負環中不斷迭代。本題中就使用了棧來代替佇列,否則會TLE。

參考文獻

Acwing-演算法提高課-圖論章節
https://www.acwing.com/activity/content/introduction/16/