差分约束
差分约束
前言
又是 20231012联考 T4 考到。。。
于是不会,前面的题也没有补,开始学习!
定义
差分约束是什么,看起来和图论没有一点关系。。。
差分约束系统是一种特殊的
每个条件都是形如
发现把这个式子变形一下就是
这个东西和单源最短路中的
于是我们考虑把
好妙哦
过程
建出图之后,我们再建立一个源点
向每一个点链接一条长度为
如果图中存在负环,于是差分约束无解,
否则,
例题
先来考虑如何判负环——
P3385 【模板】负环
负环就是在用 spfa 跑的过程中,如果入队次数超过了
时间复杂度有可能被卡到
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int T,n,m,head[N],tot=0,cnt[N],dis[N];
struct edge{
int v,nxt,w;
}e[N<<1];
bool vis[N];
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void add(int u,int v,int w){
e[++tot]=(edge){v,head[u],w};
head[u]=tot;
}
void spfa(){
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
dis[1]=0;vis[1]=true;
queue<int> q;q.push(1);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=false;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
if(++cnt[v]>=n){puts("YES");return;}
q.push(v);vis[v]=true;
}
}
}
}
puts("NO");
}
int main(){
/*2023.10.13 H_W_Y P3385 【模板】负环 spfa*/
T=read();
while(T--){
n=read();m=read();
memset(head,0,sizeof(head));tot=0;
memset(cnt,0,sizeof(cnt));
for(int i=1,u,v,w;i<=m;i++){
u=read();v=read();w=read();
add(u,v,w);
if(w>=0) add(v,u,w);
}
spfa();
}
return 0;
}
P5590 赛车游戏
有些时候一定不要忘记打标记!
你发现边权的范围是
对于有向边
于是我们把式子转化一下,就变成了:
我们就可以用差分约束做了。
具体就是先用 dfs 找出在
建出一个新图,再跑 spfa 判负环即可。
最后输出就是通过判断
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,head[N],tot=0,dis[N],m,cnt[N],h[N],t=0;
struct edge{
int v,nxt,w;
}e[N<<1],E[N<<1];
struct node{
int u,v;
}a[N<<1];
bool vis[N],pp[N];
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void print(int x,char s){
int p[15],tmp=0;
if(x==0) putchar('0');
if(x<0) putchar('-'),x=-x;
while(x){
p[++tmp]=x%10;
x/=10;
}
for(int i=tmp;i>=1;i--) putchar(p[i]+'0');
putchar(s);
}
void add(int u,int v,int w){
e[++tot]=(edge){v,head[u],w};
head[u]=tot;
}
void add2(int u,int v){
E[++t]=(edge){v,h[u]};
h[u]=t;
}
bool spfa(){
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
dis[1]=0;vis[1]=true;
queue<int>q;q.push(1);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=false;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
if(++cnt[v]>n) return true;
q.push(v),vis[v]=true;
}
}
}
}
return false;
}
bool dfs(int u){
if(u==n||pp[u]) return true;
for(int i=h[u];i;i=E[i].nxt){
int v=E[i].v;
if(!vis[i]){
vis[i]=true;
if(dfs(v)){
pp[u]=true;
add(u,v,9);add(v,u,-1);
}
}
}
return pp[u];
}
int main(){
/*2023.10.13 H_W_Y P5590 赛车游戏 差分约束*/
n=read();m=read();
for(int i=1,u,v;i<=m;i++) u=read(),v=read(),add2(u,v),a[i]=(node){u,v};
if(!dfs(1)||spfa()){puts("-1");return 0;}
print(n,' ');print(m,'\n');
for(int i=1;i<=m;i++){
int ans=dis[a[i].v]-dis[a[i].u];
print(a[i].u,' ');print(a[i].v,' ');
if(ans>=1&&ans<=9) print(ans,'\n');
else puts("1");
}
return 0;
}
ARC177F-Gateau
现在回到那联考题。
我们通过前缀和+二分分析了以下内容。
现在我们考虑一个答案
由于题目中是求每
所以我们设
于是发现其实
所以就有了一个对于
现在考虑如何构造,
由于
- 若
,说明前面已经满足条件,则 。 - 若
,则 ,这样可以保证 - 若
,则 。
发现这样枚举只用算前面
很容易想到,最后的
你发现
因为在
于是就可以用二分解决。
于是就得到了一个双重二分的方法,外层枚举
时间复杂度
现在发现
于是我们就可以用差分约束来做一做。
下标从
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,c[N],head[N],tot=0,dis[N],cnt[N],l,r,dep[N];
bool vis[N];
struct edge{
int v,nxt,w;
}e[N<<1];
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void add(int u,int v,int w){
e[++tot]=(edge){v,head[u],w};
head[u]=tot;
}
bool spfa(){
memset(vis,false,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
memset(dep,0,sizeof(dep));
dis[0]=0;vis[0]=true;
queue<int> q;q.push(0);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=false;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
dep[v]=dep[u]+1;
if(dep[v]>(n<<1)) return false;
if(!vis[v]){
q.push(v);vis[v]=true;
if(dis[q.back()]<dis[q.front()]) swap(q.front(),q.back());
}
}
}
}
return true;
}
bool chk(int x){
int L,R;
for(int i=0;i<=(n<<1);i++) head[i]=cnt[i]=0;tot=0;
for(int i=1;i<=n;i++){
L=c[i];R=x-c[i+n];
if(L>R) return false;
add(i,i+n,R);add(i+n,i,-L);
}
for(int i=1;i<=(n<<1);i++) add(i,i-1,0);
add(0,(n<<1),x);add((n<<1),0,-x);
return spfa();
}
int main(){
n=read();
for(int i=1;i<=(n<<1);i++) c[i]=read(),r=max(r,c[i]);
l=0;r<<=1;
while(l<r){
int mid=(l+r)/2;
if(chk(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
现在考虑如何优化?
发现我们判负环跑
于是我们考虑高级的操作——人脑判负环!
把这张图画出来,发现特别特殊,
现在我们考虑把这个图进行分层,于是就变成了这样:
发现只有两条红色的边会构成环,
于是负环上面要么包含
于是我们把红边删掉,
从
如果最短路是负数——那么一定存在负环。
用
时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+5;
const ll inf=1e18;
int n,c[N],l,r;
ll dis[N];
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void wrk(int x){
for(int i=2*n-1;i>n;i--){
dis[i]=min(dis[i+1],dis[i-n+1]+x-c[i+1]);
dis[i-n]=min(dis[i-n+1],dis[i]-c[i-n+1]);
}
}
void init(){for(int i=0;i<=(n<<1);i++) dis[i]=inf;}
bool chk(int x){
init();dis[0]=0;dis[(n<<1)]=x;wrk(x);//从 0 出发跑
if(dis[1]<0||dis[n+1]-c[1]<0||dis[(n<<1)]-c[1]-c[n+1]<0) return false;
init();dis[n]=0;wrk(x);//从 n 出发,先把前面的点更新了
dis[0]=min(dis[0],dis[1]);dis[(n<<1)]=min(dis[(n<<1)],dis[0]+x);//处理 n~2n 里面的初值
wrk(x);//更新 n ~ 2n 从 n 出发的值
if(dis[0]+x-c[n+1]<0||dis[n+1]<0||dis[(n<<1)]-c[n+1]<0) return false;
init();dis[(n<<1)]=0;wrk(x);//从 2n 开始跑
dis[0]=min(dis[0],dis[1]);
if(dis[0]+x-c[(n<<1)+1]+x-c[n+1]<0||dis[n+1]+x-c[(n<<1)+1]<0) return false;
return true;
}
int main(){
n=read();
for(int i=1;i<=(n<<1);i++) c[i]=read(),r=max(r,c[i]);
for(int i=1;i<=n;i++) l=max(l,c[i]+c[i+n]);
c[(n<<1)+1]=c[1];r<<=1;
while(l<r){
int mid=(l+r)/2;
if(chk(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}
Conclusion
差分约束时间复杂度过大时可以考虑人脑判负环。
本文作者:H_W_Y
本文链接:https://www.cnblogs.com/H-W-Y/p/chafenyueshu.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步