SDSC整理(Day2 差分约束)
差分约束算法
差分约束算法是一种可以帮助你解数学题的算法
\(lead\)
给你一组包含 \(m\) 个不等式,有 \(n\) 个未知数的形如
\(\begin{cases}x_{c1}-x_{c1\prime}≤y_1\\x_{c2}-x_{c2\prime}≤y_2\\......\\x_{cm}-x_{cm\prime}≤y_m\end{cases}\)
的不等式组,求任意一组这个不等式组的解。
。。。。。。
没学差分约束的我:数学题!!!
不,这是一道图论题。
话说dp也可以转图论,万物皆可图论?
\(pre-knowledge\)
\(SPFA\) ,它死了,但是没完全死
\(description\)
差分约束系统是一种特殊的 \(n\) 元一次不等式组,
它包含 \(n\) 个变量个 \(m\) 个约束条件,
每个约束条件都是两个变量做差形成的,
形如 \(x_i-x_j≤ c_k\) , \(c_k\) 是一个常数,
差分约束算法的实现就是求出一组解,使其满足所有的约束条件。
我们来看其中的一个约束条件:
我们对这个式子进行变形,得到
看着上面这个式子,你是否感到十分地熟悉???
这不就是我们的最短路吗!!!
所以,我们就把求解不等式的问题转换成了最短路的问题,
上面的 \(i\) 就对应着 \(v\) ,上面的 \(j\) 就对应着 \(u\) ,上面的 \(c_k\) 就对应着 \(w[u-v]\) ,我们就可以建一条从 \(j\) 到 \(i\) 的边权为 \(c_k\) 的路径,然后跑最短路,每一个 \(dis[i]\) 的值就对应这相应的 \(x_i\) 的值。
我们考虑无解的情况,如果存在负环,则最短路无解,该不等式组无解。
因为负环会卡掉 \(dijkstra\) ,所以我们要用 \(SPFA\) 跑最短路,关于 SPFA,它又活了,统计一下每一个点入队的次数,如果超过所有点的数量,证明出现了负环。
不会负环判断的可以先做这道题 P3385 【模板】负环
好的,现在我们就可以轻松的切掉模板题。
code
/*
差分约束
date:2022.7.26
worked by respect_lowsmile
*/
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+5;
const int INF=0x3f3f3f3f;
struct node
{
int to,next,w;
};
node edge[N<<1];
int num,flag,n,m;
int head[N],dis[N],vis[N],cnt[N];
void add(int u,int v,int w)
{
num++;
edge[num].to=v;
edge[num].w=w;
edge[num].next=head[u];
head[u]=num;
}
void SPFA()
{
queue<int> q;
for(int i=1;i<=n;++i)
dis[i]=INF;
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
dis[0]=0,vis[0]=1;
q.push(0);
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
for(int i=head[now];i;i=edge[i].next)
{
int v=edge[i].to,w=edge[i].w;
if(dis[now]+w<dis[v])
{
dis[v]=dis[now]+w;
if(!vis[v])
{
vis[v]=1,cnt[v]++;
if(cnt[v]==n+1)
{
flag=1;
return ;
}
q.push(v);
}
}
}
}
return ;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i) add(0,i,0);
for(int i=1;i<=m;++i)
{
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
add(v,u,w);
}
SPFA();
if(flag==1) printf("NO");
else
{
for(int i=1;i<=n;++i)
printf("%d ",dis[i]);
}
return 0;
}
关于最短路的两种跑法和 \(dis\) 的初值问题
这也是我在学差分约束时最不理解的地方,希望对大家有帮助。
跑法问题
上面程序运行的结果是 \(0\) ,\(-2\) , \(0\) ,考虑为什么。
你会发现我加了一句 for(int i=1;i<=n;++i) add(0,i,0);
在最短路中这是什么意思???
\(x_i≤x_0+0\) , \(x_0\) 也就是 \(dis[0]=0\) ,也就相当于 \(x_i≤ 0\) 。
能明白为什么出现这样的结果了吧。
可以发现我们相当于建了一个超级源点 \(0\) ,然后跑的最短路,那为什么要建这个超级源点呢???因为图不一定联通啊,如果不建超级源点而从 \(1\) 开始,那么就会有不与 \(1\) 联通的点更新不到,结果就会出错。
当然,不建超级源点也是可以的,我们可以在初始化的时候把所有的点全部都入队,这样就可以保证更新到所有的点。
\(dis\) 的初值问题
此外,如果你是建了超级源点跑的 \(SPFA\) ,那么 \(dis[]\) 的初值就对于结果没有任何的贡献,只是为了在跑最短路的时候方便更新路径,
但是如果使用不建超级源点的跑法,那么 \(dis[]\) 的值就相当于你跑最短路的基础,也就是相当于最短路结果的上界和最长路结果的下界,
所以如果你不建超级源点, \(dis[]\) 的初值设为 \(100000\) ,那么你会发现你的模板题跑出的结果是 \(0\) , \(99998\) , \(100000\) 。
最短路和最长路问题
最长路求的是最小值
最长路的模型:
所以我们要把式子都转化为 \(x_i≥x_j+c_k\) 的形式,然后连一条 \(j\) 到 \(i\) 的权值为 \(c_k\) 的边。
无解的情况就是有正环。
最短路求的是最大值
最短路在上面都说了,这里不在赘述。
一些式子的转换(最长路为例)
\(a=b\) \(\Rightarrow\) \(a \ge b-0\) && \(b\ge a-0\)
\(a<b\) \(\Rightarrow\) \(b \ge a+1\)
\(a \ge b\) \(\Rightarrow\) \(a \ge b+0\)
\(a>b\) \(\Rightarrow\) \(a \ge b+1\)
\(a \le b\) \(\Rightarrow\) \(b \ge a+0\)
差分约束的一些应用
本板块可能会持续更新......
前缀和的差分约束
简单题:P1250 种树
我们以简单题为例
很明显,要求最少的树的数目个数,所以是跑最长路。
我们用前缀和维护区间的树的个数,很明显, \(bi\) 到 \(ci\) 之间的树的个数就是 \(sum[c_i]-sum[b_i-1]\) 。
我们可以很明显地找到约束条件:
第一个条件是因为两个点之间的树的数量不小于 \(t_i\) ,第二和第三个条件是因为每个单位最多只能种一棵树。
我们用 \(dis[]\) 维护 \(sum[]\) ,那么我们的结果就是 \(sum[n]\) ,也就是 \(SPFA\) 跑出来的 \(dis[n]\) 。
code
/*
差分约束
date:2022.7.27
worked by respect_lowsmile
*/
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+5;
const int INF=0x3f3f3f3f;
struct node
{
int to,next,w;
};
node edge[N<<2];
int num,n,h;
int head[N],dis[N],vis[N];
void add(int u,int v,int w)
{
num++;
edge[num].to=v;
edge[num].w=w;
edge[num].next=head[u];
head[u]=num;
}
void SPFA()
{
queue<int> q;
for(int i=1;i<=n;++i)
dis[i]=-INF;
dis[0]=0,vis[0]=1;
q.push(0);
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
for(int i=head[now];i;i=edge[i].next)
{
int v=edge[i].to,w=edge[i].w;
if(dis[v]<dis[now]+w)
{
dis[v]=dis[now]+w;
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
}
int main()
{
scanf("%d %d",&n,&h);
for(int i=1;i<=h;++i)
{
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
add(u-1,v,w);
}
for(int i=1;i<=n;++i)
{
if(i) add(i-1,i,0);
if(i!=n) add(i,i-1,-1);
}
SPFA();
printf("%d",dis[n]);
return 0;
}
综合题的解法和简单题是一样的,我们假设综合题大家也会了
\(end\)
差分约束其实最重要的就是寻找题目中的不等关系,然后构造不等式求解,要注意题目中的隐含条件。
continue.....