差分约束学习笔记
差分约束学习笔记
差分约束的题目通常是给你一些 xi 和 xj 的关系式,求是否有可行解等。
而这类题目的技巧也很简单,就是连边建图即可。
这里给出一些关系式的建图的方法
xi≤xj+c⇒jc→ixi≥xj+c⇒i−c→jxi=xj+c⇒i−c→j,jc→ixi=c⇒0c→i,i−c→0
当然这里的关系式也可以变成乘法,可以用对数来转化。
然后跑最短路,如果有负环之类的就是没有可行解,否则这里最短距离就是一个解。
这里看几道题目。
P5960 【模板】差分约束 - 洛谷 (luogu.com.cn)
这题先连边建图,再用spfa来判负环即可。板子题。
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N=5E3+5;
int n,m;
vector<pii>e[N];
int dis[N],tot[N],flag[N];
void spfa(){
memset(dis,0x3f,sizeof dis);
dis[0]=0;
queue<int>q;
q.push(0);
while(!q.empty() ){
int u=q.front();
q.pop() ;
for(auto tmp:e[u]){
int v=tmp.first,w=tmp.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
tot[v]++;
if(tot[v]>=n){
cout<<"NO"<<endl;
exit(0);
}
q.push(v);
}
}
}
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1,a,b,c;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
e[b].push_back({a,c});
}
for(int i=1;i<=n;i++)e[0].push_back({i,0});
spfa();
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
这题需要转化成前缀和的形式,连边,但是我们发现相邻的前缀和差值只能是零和一之间,所以还需要建额外的边,而这题的数据范围告诉我们,必须用迪杰斯特拉跑最短路,所以要把这里的前缀设为 0
的前缀才可以。
// LUOGU_RID: 203485771
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N=5E5+5;
int n,m;
vector<pii>e[N];
int dis[N];
void dij(){
memset(dis,0x3f,sizeof dis);
dis[0]=0;
priority_queue<pii,vector<pii>,greater<pii>>q;
q.push({0,0});
while(!q.empty() ){
int u=q.top().second,l=q.top ().first;
q.pop();if(l!=dis[u])continue;
for(auto tmp:e[u]){
int v=tmp.first,w=tmp.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push({dis[v],v});
}
}
}
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1,a,b,c,op;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
a--;
e[a].push_back({b,b-a-c});
}
for(int i=1;i<=n;i++)
e[i].push_back({i-1,0});
for(int i=1;i<=n;i++)
e[i-1].push_back({i,1});
dij();
for(int i=1;i<=n;i++)
if(dis[i]-dis[i-1])printf("0 ");
else printf("1 ");
return 0;
}
[P4926 1007] 倍杀测量者 - 洛谷 (luogu.com.cn)
这题比较特殊,因为其中的改动是用乘来进行的,而这个就可以用对数来转化了,当然需要二分处理,但是其实总体难度不大,主要是观察到这个乘法可以用对数处理即可,还有小数的精度问题。
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N=1E5+5,inf =1e9+7;
const double eps =1e-5;
double dis[N],a[N];
int n,m,t,ti[N];
struct node{
int v;
double k;
int op;
};
vector<node>e[N];
bool check(double mid){
for(int i=0;i<=n;i++)dis[i]=-inf,ti[i]=0;
ti[n+1]=1;
dis[n+1]=0;
queue<int>q;
q.push(n+1);
while(!q.empty()){
int x=q.front() ;
q.pop() ;
for(auto tmp:e[x]){
int op=tmp.op,v=tmp.v;
double w;
if(op==0)w=tmp.k;
else if(op==1)w= log(tmp.k - mid);
else w= -log(tmp.k +mid);
if(dis[v]<dis[x]+w){
dis[v]=dis[x]+w;
ti[v]++;
if(ti[v]>=n)return false;
q.push(v);
}
}
}
return true;
}
signed main(){
scanf("%d%d%d",&n,&m,&t);
for(int i=1,op,a,b;i<=m;i++){
double k;
scanf("%d%d%d%lf",&op,&a,&b,&k);
e[b].push_back({a,k,op});
}
for(int i=1,x;i<=t;i++){
scanf("%d",&x),scanf("%lf",&a[x]);
e[0].push_back({x,log(a[x]),0});
e[x].push_back({0,-log(a[x]),0});
}
for(int i=0;i<=n;i++){
e[n+1].push_back({i,0,0});
}
if(check(0)){
cout<<-1;
return 0;
}
double l=0,r=10.0;
while(r-l>eps){
double mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid;
}
printf("%lf",l);
return 0;
}
[P2474 SCOI2008] 天平 - 洛谷 (luogu.com.cn)
这题个人感觉还是不错的,不像其他的一些题目,一眼就是什么是否可行之类的,这题是让我们求方案数。
然而我们发现这里的 n
非常的小,在最短路里有这种很小的 n
基本上是让我们使用 Floyd
的解法。
既然是差分约束,我们就可以直接建图了。
可以通过这个两点的最大差值和最小差值建图即可。
最后的计数需要注意,这里必须是保证能大于或小于或等于才可以。
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
int n,a,b,mx[55][55],mi[55][55];
char s[55][55];
vector<pii>e[55];
signed main() {
scanf("%d%d%d",&n,&a,&b);
for(int i=1; i<=n; i++) {
scanf("%s",s[i]+1);
for(int j=1; j<=n; j++) {
if (i == j || s[i][j] == '=') mx[i][j] = mi[i][j] = 0;
else if (s[i][j] == '+') mx[i][j] = 2, mi[i][j] = 1;
else if (s[i][j] == '-') mx[i][j] = -1, mi[i][j] = -2;
else if (s[i][j] == '?') mx[i][j] = 2, mi[i][j] = -2;
}
}
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
mx[i][j]=min(mx[i][k]+mx[k][j],mx[i][j]),
mi[i][j]=max(mi[i][k]+mi[k][j],mi[i][j]);
int l=0,r=0,m=0;
for(int i=1;i<=n;i++){
if(i==a||i==b)continue;
for(int j=1;j<i;j++){
if(j==a || j==b)continue;
//a-i>j-b
if(mi[a][i]>mx[j][b]||mi[a][j]>mx[i][b])l++;
if((mi[a][i]==mx[j][b] && mi[a][i]==mx[a][i ]&& mi[j][b]==mx[j][b])||(mi[a][j]==mx[i][b]&& mi[a][j]==mx[a][j ]&& mi[i][b]==mx[i][b]))m++;
if(mx[a][i]<mi[j][b]||mx[a][j]<mi[i][b])r++;
}
}
cout<<l<<" "<<m<<" "<<r<<endl;
return 0;
}
整体来说差分约束主要还是看出他是这个算法,实现还是不难的。