差分约束学习笔记

差分约束学习笔记

差分约束的题目通常是给你一些 xixj 的关系式,求是否有可行解等。

而这类题目的技巧也很简单,就是连边建图即可。

这里给出一些关系式的建图的方法

xixj+cjcixixj+cicjxi=xj+cicj,jcixi=c0ci,ic0

当然这里的关系式也可以变成乘法,可以用对数来转化。

然后跑最短路,如果有负环之类的就是没有可行解,否则这里最短距离就是一个解。

这里看几道题目。

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;
}

G - 01Sequence (atcoder.jp)

这题需要转化成前缀和的形式,连边,但是我们发现相邻的前缀和差值只能是零和一之间,所以还需要建额外的边,而这题的数据范围告诉我们,必须用迪杰斯特拉跑最短路,所以要把这里的前缀设为 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;
}

整体来说差分约束主要还是看出他是这个算法,实现还是不难的。

posted @ 2025-02-18 22:22  hnczy  阅读(9)  评论(0编辑  收藏  举报