差分约束系统
概念
- 一种特殊的 n n n 元一次不等式组,包含 n n n 个变量以及 m m m 个约束条件
- 每个约束条件是由两个其中的变量做差构成的,形如 x i − x j ≤ c k x_i-x_j≤c_k xi−xj≤ck ,其中 c k c_k ck 是常数
- 要解决的问题:求出一组界 x 1 = a 1 , x 2 = a 2 , . . . , x n = a n x_1=a_1,x_2=a_2,...,x_n=a_n x1=a1,x2=a2,...,xn=an,使得所有的约束条件得到满足,否则判断出无解
- 如果 { a 1 , a 2 , a 3 , . . . , a n } \{a_1,a_2,a_3,...,a_n\} {a1,a2,a3,...,an} 是该系统的一组解,那么对于任意的常数 d d d, { a 1 + d , a 2 + d , a 3 + d , . . . , a n + d } \{a_1+d,a_2+d,a_3+d,...,a_n+d\} {a1+d,a2+d,a3+d,...,an+d} 也是该差分约束系统的一组解
与图论模型的联系
- 求解差分约束系统,都可以转化为图论中的单源最短(长)路问题
- 用最短路径求差分方程的最大解
- 对于不等式 x i − x j < = c k x_i-x_j<=c_k xi−xj<=ck,移项得 x i < = c k + x j x_i<=c_k+x_j xi<=ck+xj,把 x i x_i xi 与 x j x_j xj 看作是图中的两个结点,则该不等式表示的是从结点 x j x_j xj 到结点 x i x_i xi 有一条边权为 c k c_k ck 的有向边,于是就转换成了单源最短路问题
- 如果最终令 x 1 = 0 x_1=0 x1=0,那么 x i = d i s [ i ] x_i=dis[i] xi=dis[i] 便是差分约束问题的一组解
- 求解方法: S P F A SPFA SPFA 与 D i j k s t r a Dijkstra Dijkstra
- 用最长路径求差分方程的最小解.
- 对于不等式 x i − x j > = c k x_i-x_j>=c_k xi−xj>=ck,移项得 x i > = c k + x j x_i>=c_k+x_j xi>=ck+xj,把 x i x_i xi 与 x j x_j xj 看作是图中的两个结点,则该不等式表示的是从结点 x j x_j xj 到结点 x i x_i xi 有一条边权为 c k c_k ck 的有向边,于是就转换成了单源最长路问题
- 如果最终令 x 1 = 0 x_1=0 x1=0,那么 x i = d i s [ i ] x_i=dis[i] xi=dis[i] 便是差分约束问题的一组解
- 求解方法: S P F A SPFA SPFA 与 D i j k s t r a Dijkstra Dijkstra
- S P F A SPFA SPFA 只需将松弛部分的符号取反
-
D
i
j
s
k
t
r
a
Dijsktra
Dijsktra 由于最长路没有最优子结构,所以一个点从堆中弹出,并不一定是最长路,修改方法为
- 小根堆改为大根堆
- 松弛条件符号取反
- 允许重复出堆
void spfa(int s) {
std::queue<int> q;
for(int i = 0; i <= n; ++i) dis[i] = -inf, vis[i] = 0;
q.push(s);
dis[s] = 0;
vis[s] = 1;
while(!q.empty()) {
int u = q.front(); q.pop();
vis[u] = 0;
for(int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to;
if(dis[v] < dis[u] + e[i].w) {
dis[v] = dis[u] + e[i].w;
if(!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
}
void dijkstra1(int s) {
std::priority_queue<std::pair<int,int> > q;
for(int i = 0; i <= n; ++i) dis[i] = -inf;
dis[s] = 0;
q.push({dis[s],s});
while(!q.empty()) {
int u = q.top().second, W = q.top().first; q.pop();
if(W < dis[u]) continue;
for(auto it:G[u]){
int v = it.first, w = it.second;
if(dis[v] < dis[u] + w){
dis[v] = dis[u] + w;
q.push({dis[v],v});
}
}
}
}
解的存在性
- 在求解最短路的过程中可能会出现以下情况
- 存在负环, x i − x 1 ≤ T x_i-x_1≤T xi−x1≤T 中的 T为无限小, x i x_i xi 的结果不存在
- 终点不可达,表示 x i x_i xi 与 x 1 x_1 x1 之间没有约束关系, x i x_i xi 的结果可以是无限大,对应的是 d i s [ i ] = i n f dis[i]=inf dis[i]=inf
- 以上情况均无解
常用转化
- 给出 x i − x j ≥ T x_i − x_j ≥ T xi−xj≥T,可以移项转化为 x j − x i ≤ − T x_j − x_i ≤- T xj−xi≤−T
- 给出 x i − x j < T x_i − x_j < T xi−xj<T,在整数域上可以转化为 x i − x j ≤ T − 1 x_i − x_j ≤ T-1 xi−xj≤T−1
- 给出 x i − x j = T x_i − x_j = T xi−xj=T,可以转化为 x i − x j ≤ T x_i − x_j ≤ T xi−xj≤T 且 x i − x j ≥ T x_i-x_j≥T xi−xj≥T
- x i x j ≤ T \frac{x_i}{x_j}≤T xjxi≤T,两边同时取对数进行转化
拓扑排序
- 在一个 D A G DAG DAG(有向无环图)中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 u u u 到 v v v 的有向边 ( u , v ) (u, v) (u,v) , 都可以有 u u u 在 v v v 的前面。
- 给定一个 D A G DAG DAG,如果从 u u u 到 v v v 有边,则认为 v v v 依赖于 u u u 。如果 u u u 到 v v v 有路径 ( u ( u (u 可达 v ) v ) v),则称 v v v 间接依赖于 u u u 。
- 拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。
Kahn算法
- 将入度为 0 0 0 的点组成一个集合 S S S
- 每次从 S S S 里面取出一个顶点 u u u (可以随便取)放入 L L L , 然后遍历顶点 u u u 的所有边 ( u , v ) (u, v) (u,v) , 并删除之,并判断如果该边的另一个顶点 v v v,在移除这一条边后入度为 0 0 0 , 那么就将这个顶点放入集合 S S S 中。不断地重复取出顶点然后重复这个过程……
- 最后当集合为空后,就检查图中是否存在任何边。如果有,那么这个图一定有环路,否者返回
L
L
L ,
L
L
L 中顺序就是拓扑排序的结果
bool TopoSort(){
queue<int> q;
for(int i=0;i<n;i++)
if(indegree[i]==0)
q.push(i);
vector<int> ans;
while(!q.empty()){
int u=q.front();
q.pop();
ans.push_back(u);
for each edge(u,v){
if(--indegree[v]==0)
q.push(v);
}
}
if(ans.size()==n){
for(int i=0;i<n;i++)
cout<<ans[i]<<" ";
cout<<endl;
return true;
}
else
return false;
}
若要按字典序生成拓扑序列,则把 queue 改为 priority_queue 即可
强连通分量(SCC)
Kosaraju 算法
- 可以找到有向图中所有的SCC
- 第一遍 dfs 确定原图的逆后序序列
- 第二遍 dfs 在反图中按照逆后序序列进行遍历
- 反图即将原图中的有向边反向
- 每次由起点遍历到的点即构成一个 SCC
算法模拟
- DFS 序列
- 前序序列:第一次达到点 x 的次序,用 d[x] 表示
- 后序序列:x 点遍历完成的次序,即回溯时间,用 f[x] 表示
- 逆后序序列:后序序列的逆序
const int maxn=10010;
//dnt - dfs序计数
//scnt - scc计数
//dfn[i] - dfs后序序列中第i个点
//c[i] - i号点所在 scc编号
int n,m,a,b,c[maxn],dfn[maxn],SCC[maxn],dcnt,scnt;
bool vis[maxn];
//G1-原图,G2-反图,G3-缩点后的原图
vector<int> G1[maxn],G2[maxn],G3[maxn];
void init(int nn){
dcnt=scnt=0;
for(int i=1;i<=n;i++){
vis[i]=0;
c[i]=0;
SCC[i]=0;
}
}
void dfs1(int x){
vis[x]=1;
for(int i=0;i<G1[x].size();i++){
if(!vis[G1[x][i]])
dfs1(G1[x][i]);
}
dfn[++dcnt]=x;
}
void dfs2(int x){
c[x]=scnt;
SCC[scnt]++;
for(int i=0;i<G2[x].size();i++){
if(!c[G2[x][i]])
dfs2(G2[x][i]);
}
}
void kosaraju(){
init(n);
for(int i=1;i<=n;i++)
if(!vis[i])
dfs1(i);
for(int i=n;i>=1;i--){
if(!c[dfn[i]]){
++scnt;
dfs2(dfn[i]);
}
}
}
//缩点
void MergePoint(){
for(int x=0;x<n;x++){
for(int i=0;i<G1[x].size();i++){
if(c[x]==c[G1[x][i]])
continue;
G3[c[x]].push_back(c[G1[x][i]]);
}
}
}
相关例题
https://blog.csdn.net/weixin_44771757/article/details/105439877