📂算法
🔖算法
2023-11-22 13:25阅读: 5评论: 0推荐: 0

差分约束学习指南

前置芝士

求解差分约束系统,有 m条约束条件,每条都为形如 (xaxbck)(xaxbck)xa=xb 的形式,判断该差分约束系统有没有解。

题意 转化 连边
(xaxbc) (xbxac) add(a, b, -c);
("xaxbc") (xaxbc) add(b, a, c);
(xa=xb) (xaxb0, xbxa0) add(b, a, 0), add(a, b, 0);

差分约束

给出一组包含 m 个不等式,有 n 个未知数的形如:

{xc1xc1y1xc2xc2y2xcmxcmym

的不等式组,求任意一组满足这个不等式组的解。

[input]

第一行为两个正整数 n,m,代表未知数的数量和不等式的数量。

接下来 m 行,每行包含三个整数 c,c,y,代表一个不等式 xcxcy

[output]

一行,n 个数,表示 x1,x2xn 的一组可行解,如果有多组解,请输出任意一组,无解请输出 NO

[a.in]

3 3
1 2 3
2 3 -2
1 3 1

[a.out]

5 3 5

{x1x23x2x32x1x31

一种可行的方法是 x1=5,x2=3,x3=5

{53=2335=2255=01

[datas]

1n,m5×103104y1041c,cncc

solved

我们观察下这些不等式,像在最短路里的三角不等式,于是,发现了这些性质:

我们设 dis 数组代表长度,跟最短路里的dis 概念一样。

disvdisu+w(uv)disvdisuw(uv)

所以,我们只要对于每个不等式 xixjy,连一条从j 到i 长度为y 的边,跑一遍最短路,即可得到一组解。备注:我们设 cnt 数组来表示这个点记录了几次,如果这个图有负环,说明 cntin,则要输出无解。这就是差分约束算法的重要思想。

[寻找所有x<=0最大解]

因此对于这类不等式组的求解,我们可以将其抽象成一个有 n 个点的最短路问题,对于不等式xixjy,建一条从j连向i 边权为y的单向边。

补充: 如果存在符号相反的不等式,如xixjy,我们可以通过给两边乘−1 的方式使其变号,变为 xjxiy

建完图后,为了每个点的可达性,我们要新建一个超级源点s=n+1 向每个点连出一条边权为 0的边。

const int N=5010;
int n,m,h[N],idx;
struct edge{
	int v,ne,w;
}e[N<<1];
int dis[N],cnt[N];
bool vis[N];
queue<int> q;
void add(int u,int v,int w){
	e[++idx].v=v;
	e[idx].ne=h[u];
	e[idx].w=w;
	h[u]=idx;
}
bool spfa(int s){
	dis[s]=0;
	vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=0;
		for(int i=h[u];i;i=e[i].ne){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(vis[v]==0){
					cnt[v]++;
					if(cnt[v]>=n+1) return false;//多加了一个超级源点,所以总共的点数是n+1,最多也就被每个点更新一次
					vis[v]=1;
					q.push(v);
				}

			}
		}
	}
	return true;
}
void solve(){
	cin>>n>>m;
	int s=n+1;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		add(v,u,w);
	}
	for(int i=1;i<=n;i++){
		add(s,i,0);
	}
	memset(dis,0x3f,sizeof(dis));
	if(!spfa(s)){
		cout<<"NO"<<endl;
		return;
	}
	for(int i=1;i<=n;i++)
			cout<<dis[i]<<" ";
	cout<<endl;
}

[寻找所有x>=0的最小值]

const int N=5010;
struct edge{
	int ne,v,w;
}e[N<<1];
int idx,h[N];
int cnt[N],dis[N];
bool vis[N];
queue<int> q;
int n,m;
void add(int u,int v,int w){
	e[++idx]={h[u],v,w};
	h[u]=idx;
}
bool spfa(int s){
	q.push(s);
	vis[s]=true;
	// cnt[s]++;
	dis[s]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=false;
		for(int i=h[u];i;i=e[i].ne){
			int v=e[i].v;
			int w=e[i].w;
			if(dis[v]<dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v]){
					q.push(v);
					vis[v]=true;
				if(++cnt[v]>=n+1) {return false;}
				}
			}
		}
	}
	return true;

}
void solve(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,-w);
	}
	int s=n+1;
	for(int i=1;i<=n;i++) add(s,i,0);
	memset(dis,-1,sizeof(dis));
	if(!spfa(s)){
		cout<<"NO"<<endl;
	}else{
		for(int i=1;i<=n;i++) cout<<dis[i]<<" ";
		cout<<endl;
	}
}

糖果

幼儿园里有 N 个小朋友,lxhgww 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,lxhgww 需要满足小朋友们的 K 个要求。幼儿园的糖果总是有限的,lxhgww 想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

[input]

输入的第一行是两个整数 NK。接下来 K 行,表示这些点需要满足的关系,每行 3 个数字,XAB

  • 如果 X=1, 表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多;
  • 如果 X=2, 表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果;
  • 如果 X=3, 表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果;
  • 如果 X=4, 表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果;
  • 如果 X=5, 表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果;

[output]

输出一行,表示 lxhgww 老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 1

[a.in]

5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1

[a.out]

11

[datas]

对于所有的数据,保证 K100000,1X5,1A,BN

solved

我们将小朋友当做点,小朋友之间限制条件当做边。

对于X=1、X=3、X=5时,我们不难知道,想要使用最少的糖果数量,那么最好的方式就是两个人糖果数量一样。

对于X=2,那么A=B-1最优。

对于X=4,那么B=A-1最优。

(1)建图

比如A要少于B,则建一条A->B的边,这样拓扑排序下来,可以保证处理每个点的糖果数量时,可以从小处理到大,符合上面的要求。我们就需要额外记录边权,X=1、3、5边权为0,X=2、4边权为1。X=1时需要建双向边!即A->B且B->A。

(2)判断无解

在建新图用来拓扑排序时,看两个点是否在同一个环内,在且边权为1则无解。

(3)更新糖果数量

dp[当前更新的点] = max(dp[当前更新的点],dp[当前删除的点] + nnei[当前删除的点][第j个邻居].边权)

(4)总体思路:建图(X=1:建边权为0的双向边,X=2、X=4建边权为1的单向边,X=3、X=5时建边权为0的单向边)——>Tarjan——>边建新图边判断无解——>用新图拓扑排序,并更新每个点所需要的糖果数量。

// A要少于B,则建一条A->B的边
const int N = 100010;
int n, k;

/*
scc:每个节点的归属新编号
cnt:缩点个数
low:最小可到达时间戳
dfn:每个节点的深度优先搜索时间戳
idx:时间
*/
int scc[N], cnt, low[N], dfn[N], idx, tot[N];
bool ins[N];
stack<int> s;
vector<node> e[N];
vector<node> newe[N];
struct node {
	int ne;
	int v;
};

int dp[N];
int in[N];
ll ans;

void tarjan(int u) {
	low[u] = dfn[u] = ++idx;
	ins[u] = true;
	s.push(u);
	int len = e[u].size();
	for (int i = 0; i < len; i++) {
		int v = e[u][i].ne;
		if (dfn[v] == 0) {
			tarjan(v);
			low[u] = min(low[v], low[u]);
		} else {
			if (ins[v])
				low[u] = min(low[u], low[v]);
				//low[u]=min(low[u],dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		cnt++;
		scc[u] = cnt;
		ins[u] = false;
		tot[cnt]++;
		while (s.top() != u) {
			int t = s.top();
			ins[t] = false;
			scc[t] = cnt;
			s.pop();
			tot[cnt]++;
		}
		s.pop();
	}
}
void solve() {
	cin >> n >> k;
	for (int i = 1; i <= k; i++) {
		int t, x, y;
		cin >> t >> x >> y;
		switch (t) {
		case 1: {
			e[x].push_back((node) {y, 0});
			e[y].push_back((node) {x, 0});
			break;
		}
		case 2: {
			e[x].push_back((node) {y, 1});
			break;
		}
		case 3: {
			e[y].push_back((node) {x, 0});
			break;
		}
		case 4: {
			e[y].push_back((node) {x, 1});
			break;
		}
		case 5: {
			e[x].push_back((node){y, 0});
			break;
		}
		}
	}
		for(int i=1;i<=n;i++){
			if(dfn[i]==0) tarjan(i);
		}
		for(int i=1;i<=n;i++){
			int len=e[i].size();
			for(int j=0;j<len;j++){
				int v=e[i][j].ne;
				int xx=scc[i];
				int yy=scc[v];
				if(xx==yy&&e[i][j].v==1){
					cout<<-1<<endl;
					return;
				}
				if(xx!=yy){
					newe[xx].push_back((node){yy,e[i][j].v});
					in[yy]++;
					//会有重边
				}
			}
		}
		queue<int> q;
		for(int i=1;i<=cnt;i++){
			if(!in[i]){
				q.push(i);
				dp[i]=1;
			}
			while(!q.empty()){
				int cur=q.front();
				q.pop();
				int len=newe[cur].size();
				for(int i=0;i<len;i++){
					int v=newe[cur][i].ne;
					in[v]--;
					dp[v]=max(dp[v],dp[cur]+newe[cur][i].v);
					if(!in[v]) q.push(v);
				}
			}
		}
		for(int i=1;i<=cnt;i++){
			ans+=(ll)dp[i]*tot[i];
		}
		cout<<ans<<endl;
	}

本文作者:White_Sheep

本文链接:https://www.cnblogs.com/taotao123456/p/17848814.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   White_Sheep  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑