差分约束系列
快乐建模,快乐 spfa。
你需要更快地找负权,你可能需要更厉害的 spfa。SPFA 这一块。
我的题单。
差分约束
1.P5960【模板】差分约束
题意
解决问题形如:
给出一组包含 mmm 个不等式,有 nnn 个未知数的形如:
{xa1−xb1≤y1xa2−xb2≤y2⋯xam−xbm≤ym\begin{cases} x_{a_1}-x_{b_1}\leq y_1 \\x_{a_2}-x_{b_2} \leq y_2 \\ \cdots\\ x_{a_m} - x_{b_m}\leq y_m\end{cases}⎩⎨⎧xa1−xb1≤y1xa2−xb2≤y2⋯xam−xbm≤ym
的不等式组,求任意一组满足这个不等式组的解。
1≤n,m≤50001\le n,m\le 50001≤n,m≤5000,∀i∈[1,n]\forall i\in[1,n]∀i∈[1,n] 满足:yi∈[−104,104]y_i\in[-10^4,10^4]yi∈[−104,104],ai≠bia_i\neq b_iai=bi。
思路
拿出其中一条式子移项:
xa≤xb+yx_a\le x_b+yxa≤xb+y
这就很像最短路上松弛一条边的条件。于是想要用图论建模:我们从 j→ij\to ij→i 连一条权值为 yyy 的有向边。为了使这个有向图连通,我们新建一个 000 号点,从 000 向所有点连 nnn 条权值为 000 的边(xi≤x0+0x_i\le x_0+0xi≤x0+0,对答案没有影响)
我们称这个有向图为差分约束系统。
然后从 000 开始对整个有向图跑一个最短路,0→i0\to i0→i 的最短距离是 disidis_idisi,{dis}\{dis\}{dis} 是 {x}\{x\}{x} 的一组解。
这个图里面可能会出现环,如果跑出一个负权环 wloop<0w_{loop}<0wloop<0,设环上的某个点 uuu,那么在差分约束系统中 xu≤xu+wloopx_u\le x_u+w_{loop}xu≤xu+wloop,这显然不成立。因此当这个有向图存在负环,差分约束系统无解。
因为 yyy 的值域有负数而且要找负环,所以只能用 spfa 找负环。具体地,一条边边权为负,会把最短路引向这条边,那么从某个点开始就会沿着负环一直转圈。
我们要知道什么时候要开始转第二圈了。众所周知,n+1n+1n+1 个点的有向图,每个点的最短路的边数 ≤n\le n≤n(最劣是把所有点经过一次)。因此维护一个 cnticnt_icnti 表示 0→i0\to i0→i 最短路经过的边数,当 ∃cnti>n\exist cnt_i>n∃cnti>n,说明开始转圈,出现负环了。
具体细节见代码:
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=6005,inf=0x3f3f3f3f;
ll n,m;
struct edge
{ll to,next,w;
}e[N<<1];
ll idx,head[N];
void addedge(ll u,ll v,ll w)
{idx++;e[idx].to=v;e[idx].next=head[u];e[idx].w=w;head[u]=idx;
}
queue<ll>q;
bool vis[N];
ll dis[N],cnt[N];//最短路包含边数<=n-1
bool spfa()//找负环,找带负权最短路
{memset(dis,inf,sizeof(dis));memset(vis,0,sizeof(vis));memset(cnt,0,sizeof(cnt)); dis[0]=0;vis[0]=1;q.push(0);while(!q.empty()){ll u=q.front();q.pop();vis[u]=0;for(int i=head[u];i;i=e[i].next){ll v=e[i].to,w=e[i].w;if(dis[u]+w<dis[v]){dis[v]=dis[u]+w;cnt[v]=cnt[u]+1;if(cnt[v]>n)return 1;//条数>n连接重复点,有个超级源点,出现负环 if(!vis[v]){q.push(v);vis[v]=1;}}}}return 0;
}
int main()
{scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++)addedge(0,i,0);for(int i=1;i<=m;i++){ll u,v,w;scanf("%lld%lld%lld",&u,&v,&w);addedge(v,u,w);}if(spfa()){puts("NO");return 0;}for(int i=1;i<=n;i++)printf("%lld ",dis[i]);return 0;
}
后面的差分约束题目,都是建模了。