1015. Smallest Integer Divisible by K (M)
阿新 • • 發佈:2020-11-25
差分約束問題是求形如\(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.求不等式組的可行解
- 源點需要滿足的條件:從源點出發,一定可以走到所有的邊(否則所求結果並未滿足全部的約束條件)
步驟
- 先將每一個不等式\(x_i \leq x_j + c_k\)
- 找一個超級源點,使得該源點一定可以遍歷所有邊
- 從源點求一遍單源最短路
負環
如果存在負環,則原不等式組一定無解,反之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\)的最大值為例:
- 首先,從源點\(0\)到\(x_i\)
- 即\(x_i \leq c_0+c_1+...+c_{i-1}\),也就是這條路徑的長度
- 且需要滿足所有的不等式鏈(路徑),則應該求出所有路徑中的最小值(\(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/