【学习笔记】差分约束

前言

2024.1.27 \(huge\) 在讲不要忽略算法的细节时,以最短路和差分约束为例子。发现自己差分约束忘得差不多了,于是就有了这篇博客。

负环

  • 在一张图中,若存在一条边权之和为负数的回路,则称这个回路为负环。在一张图中,若存在一条边权之和为正数的回路,则称这个回路为正环。

  • 如果一张图中存在负环,则可以表现为在 \(SPFA\) 的迭代过程中,无论经过多少次迭代,总存在一条有向边 \((x,y,z)\) 满足 \(dis_{s,y}>dis_{s,x}+z\) ,其中 \(s\) 为起点,从而使得 \(SPFA\) 的迭代永远不能结束。

  • 代码实现

    • 判定最短路径包含的边数

      • 设 \(cnt_{s,x}\) 表示从起点 \(s\) 到 \(x\) 的最短路径包含的边数。规定 \(cnt_{s,s}=0\) 。当执行 \(dis_{s,y}=dis_{s,x}+z\) 时,同时更新 \(cnt_{s,y}=cnt_{s,x}+1\) 。此时如果有 \(cnt_{s,y} \ge n\) ,则说明图中存在从起点 \(s\) 出发能到达的负环。若迭代能够完成,即算法正常结束,则说明图中不存在从起点 \(s\) 出发能到达的负环。

        点击查看代码

        cpp 复制代码
        bool spfa(int s,int n)
        {
        	int i,x;
        	memset(vis,0,sizeof(vis));
        	memset(dis,0x3f,sizeof(dis));
        	queue<int>q;
        	q.push(s);
        	dis[s]=0;
        	vis[s]=1;
        	while(q.empty()==0)
        	{
        		x=q.front();
        		vis[x]=0;
        		q.pop();
        		for(i=head[x];i!=0;i=e[i].nxt)
        		{
        			if(dis[e[i].to]>dis[x]+e[i].w)
        			{
        				dis[e[i].to]=dis[x]+e[i].w;
        				num[e[i].to]=num[x]+1;
        				if(num[e[i].to]>=n)
        				{
        					return false;//若返回false,则说明图中存在从起点s出发能到达的负环
        				}
        				if(vis[e[i].to]==0)
        				{
        					q.push(e[i].to);
        					vis[e[i].to]=1;
        				}
        			}
        		}
        	}
        	return true;//若返回true,则说明图中不存在从起点s出发能到达的负环
        }
    • 卡时做法

      • 对于上面的判定最短路径包含的边数做法,可以根据运行时间限制,给 \(cnt_{y}\) 设定一个上界,超出时直接判定存在负环。

        点击查看代码

        cpp 复制代码
        ......//剩余代码同上
        if(num[e[i].to]>=MAX_N)//MAX_N为设定的上界
        {
        	return false;//若返回false,则说明图中存在从起点s出发能到达的负环
        }
        ......//剩余代码同上
    • 将 \(SPFA\) 从基于 \(BFS\) 实现改为基于 \(DFS\) 实现

      • 对于一条有向边 \((x,y,z)\) ,当执行 \(dis_{s,y}=dis_{s,x}+z\) 时,同时判断 \(y\) 是否已经访问过或从 \(y\) 出发是否能到达负环。对于前者,如果 \(y\) 在递归前已经被访问过,则说明图中存在从起点 \(s\) 出发能到达的负环;否则,则说明图中不存在从起点 \(s\) 出发能到达的负环。对于后者,直接转移即可。

        点击查看代码

        cpp 复制代码
        bool spfa(int s)
        {
        	vis[s]=1;
        	for(int i=head[s];i!=0;i=e[i].next)	
        	{
        		if(dis[e[i].to]>dis[s]+e[i].w)
        		{
        			dis[e[i].to]=dis[s]+e[i].w;
        			if(vis[e[i].to]==1||spfa(e[i].to)==false)
        			{
        				return false;//若返回false,则说明图中存在从起点s出发能到达的负环
        			}
        		}
        	}
        	vis[s]=0;//记得回溯
        	return true;//若返回true,则说明图中不存在从起点s出发能到达的负环
        }
        memset(dis,0x3f,sizeof(dis));
        memset(vis,0,sizeof(vis));
        dis[s]=0;
  • 例题

差分约束

  • 差分约束系统是由 \(m\) 个 \(n\) 元一次不等式组成的 \(n\) 元一次不等式组,形如 \(\begin{cases}x_{a_{1}}-x_{b_{1}} \le c_{1}\\x_{a_{2}}-x_{b_{2}} \le c_{2} \\ \dots \\ x_{a_{m}}-x_{b_{m}} \le c_{m}\end{cases}\) ,其中对于每一个 \(i(1 \le i \le n)\) 均有 \(1 \le a_{i},b_{i} \le n\) 。我们要解决的问题是:求一组解 \(\begin{cases}x_{1}=ans_{1} \\ x_{2}=ans_{2} \\ \dots \\ x_{n}=ans_{n}\end{cases}\) ,使得所有的不等式均成立或给出无解信息。
  • 代码实现
    • 对于第 \(i(1 \le i \le m)\) 个不等式 \(x_{a_{i}}-x_{b_{i}} \le c_{i}\) ,可以变形为 \(x_{a_{i}} \le x_{b_{i}}+c_{i}\) 。这与三角形不等式 \(dis_{s,y} \le dis_{s,x}+z\) 十分相似。因此我们可以把变量 \(x_{a_{i}}\) 看做有向图中的一个节点 \(a_{i}\) ,从节点 \(b_{i}\) 向节点 \(a_{i}\) 连一条 \(c_{i}\) 的有向边。
      • 由不等式基本性质,容易有若 \(\begin{cases}x_{1}=ans_{1} \\ x_{2}=ans_{2} \\ \dots \\ x_{n}=ans_{n}\end{cases}\) 是原差分约束系统的一组解,则 \(\begin{cases}x_{1}=ans_{1}+d \\ x_{2}=ans_{2}+d \\ \dots \\ x_{n}=ans_{n}+d\end{cases}\) 也是原差分约束系统的一组解,其中 \(d\) 为常数。
      • 如果题目要求给定了所有解的最小值 \(k\) 或通过如上的构图方式所构出的图不连通时,需要加入一个超级源点 \(0\) ,并令 \(x_{0}=k\) ,同时原差分约束系统增加了 \(n\) 个 \(n\) 元一次不等式 \(x_{a_{i}}-x_{0} \le k\) 。
    • 设 \(dis_{0,0}=k\) ,以 \(0\) 为起点(超级源点)求单源最短路。若图中存在负环,则给定的差分约束系统无解;否则, \(\begin{cases}x_{1}=dis_{1} \\ x_{2}=dis_{2} \\ \dots \\ x_{n}=dis_{n}\end{cases}\) 为原差分约束系统的一组解。
      • 一些比较显然的转化
        • 若对于任意一个 \(i(1 \le i \le m)\) 有 \(x_{a_{i}}-x_{b_{i}} \ge c_{i}\) 可以从节点 \(b_{i}\) 向节点 \(a_{i}\) 连一条 \(c_{i}\) 的有向边,求单源最长路,判定有无解变为是否存在正环;或者可以转化为 \(x_{b_{i}}-x_{a_{i}} \le -c_{i}\) 。
          • 同样地,若对于任意一个 \(i\) 有 \(x_{a_{i}}-x_{b_{i}} \le c_{i}\) 也可以转化为 \(x_{b_{i}}-x_{a_{i}} \ge -c_{i}\) 。
        • 若对于任意一个 \(i(1 \le i \le m)\) 有 \(x_{a_{i}}-x_{b_{i}}=c_{i}\) 可以转化为 \(\begin{cases}x_{a_{i}}-x_{b_{i}} \le c_{i} \\ x_{a_{i}}-x_{b_{i}} \ge c_{i}\end{cases}\) 。
        • 若对于任意一个 \(i(1 \le i \le m)\) 有 \(x_{a_{i}}-x_{b_{i}}>c_{i}\) 可以转化为 \(x_{a_{i}}-x_{b_{i}} \ge c_{i}+1\) 。
        • 若对于任意一个 \(i(1 \le i \le m)\) 有 \(x_{a_{i}}-x_{b_{i}}<c_{i}\) 可以转化为 \(x_{a_{i}}-x_{b_{i}} \le c_{i}-1\) 。
  • 例题
    • luogu P5960 【模板】差分约束

      • 以下两种不同的写法均可。

        单源最短路找负环

        cpp 复制代码
        void spfa(ll s,ll n)//单源最短路找负环
        {
        	ll i,x;
        	memset(vis,0,sizeof(vis));
        	memset(dis,0x3f,sizeof(dis));
        	queue<ll>q;
        	q.push(s);
        	dis[s]=0;
        	vis[s]=1;
        	while(q.empty()==0)
        	{
        		x=q.front();
        		vis[x]=0;
        		q.pop();
        		for(i=head[x];i!=0;i=e[i].nxt)
        		{
        			if(dis[e[i].to]>dis[x]+e[i].w)
        			{
        				dis[e[i].to]=dis[x]+e[i].w;
        				num[e[i].to]=num[x]+1;
        				if(num[e[i].to]>=n+1)
        				{
        					cout<<"NO"<<endl;
        					exit(0);
        				}
        				if(vis[e[i].to]==0)
        				{
        					q.push(e[i].to);
        					vis[e[i].to]=1;
        				}
        			}
        		}
        	}
        }
        int main()
        {
        	ll n,m,u,v,w,i;
        	cin>>n>>m;
        	for(i=1;i<=n;i++)
        	{
        		add(0,i,0);
        	}
        	for(i=1;i<=m;i++)
        	{
        		cin>>u>>v>>w;
        		add(v,u,w);
        	}
        	spfa(0,n);
        	for(i=1;i<=n;i++)
        	{
        		cout<<dis[i]<<" ";
        	}
        	return 0;
        }

        单源最长路找正环

        cpp 复制代码
        void spfa(ll s,ll n)
        {
        	ll i,x;
        	memset(vis,0,sizeof(vis));
        	memset(dis,-0x3f,sizeof(dis));
        	queue<ll>q;
        	q.push(s);
        	dis[s]=0;
        	vis[s]=1;
        	while(q.empty()==0)
        	{
        		x=q.front();
        		vis[x]=0;
        		q.pop();
        		for(i=head[x];i!=0;i=e[i].nxt)
        		{
        			if(dis[e[i].to]<dis[x]+e[i].w)
        			{
        				dis[e[i].to]=dis[x]+e[i].w;
        				num[e[i].to]=num[x]+1;
        				if(num[e[i].to]>=n+1)
        				{
        					cout<<"NO"<<endl;
        					exit(0);
        				}
        				if(vis[e[i].to]==0)
        				{
        					q.push(e[i].to);
        					vis[e[i].to]=1;
        				}
        			}
        		}
        	}
        }
        int main()
        {
        	ll n,m,u,v,w,i;
        	cin>>n>>m;
        	for(i=1;i<=n;i++)
        	{
        		add(0,i,0);
        	}
        	for(i=1;i<=m;i++)
        	{
        		cin>>u>>v>>w;
        		add(u,v,-w);
        	}
        	spfa(0,n);
        	for(i=1;i<=n;i++)
        	{
        		cout<<dis[i]<<" ";
        	}
        	return 0;
        }
    • luogu P1260 工程规划

    • tgHZOJ 183. 小K的农场

      • 数据弱化版:BZOJ 3436.小K的农场 | luogu P1993 小 K 的农场
      • 本题因数据经过特殊的构造,卡掉了基于 \(BFS\) 实现的 \(SPFA\) 。故需要使用 deque 优化 \(SPFA\) 或使用 \(DFS\) 式的 \(SPFA\) 。但是因为数据较水,使用卡时做法也可通过。
        • luogu P1993 小 K 的农场 经过特殊的构造,用遍历边的顺序卡掉了链式前向星的 \(DFS\) 式的 \(SPFA\) ,需要使用 vector 建图或事先打乱边。
    • AT_arc090_b [ABC087D] People on a Line

      • 不知道为啥空间开到 \(20 \times m\) 才过。

        点击查看代码

        cpp 复制代码
        struct node
        {
        	int nxt,to,w;
        }e[4000010];
        int head[4000010],dis[4000010],vis[4000010],num[4000010],cnt=0;
        void add(int u,int v,int w)
        {
        	cnt++;
        	e[cnt].nxt=head[u];
        	e[cnt].to=v;
        	e[cnt].w=w;
        	head[u]=cnt;
        }
        bool spfa(int s)
        {
        	vis[s]=1;
        	for(int i=head[s];i!=0;i=e[i].nxt)	
        	{
        		if(dis[e[i].to]<dis[s]+e[i].w)
        		{
        			dis[e[i].to]=dis[s]+e[i].w;
        			if(vis[e[i].to]==1||spfa(e[i].to)==false)
        			{
        				return false;
        			}
        		}
        	}
        	vis[s]=0;
        	return true;
        }
        int main()
        {
        	int n,m,u,v,w,i;
        	cin>>n>>m;
        	for(i=1;i<=m;i++)
        	{
        		cin>>u>>v>>w;
        		add(u,v,w);
        		add(v,u,-w);
        	}
        	for(i=1;i<=n;i++)
        	{
        		add(0,i,0);
        	}
        	memset(dis,-0x3f,sizeof(dis));
        	memset(vis,0,sizeof(vis));
        	dis[0]=0;
        	if(spfa(0)==true)
        	{
        		cout<<"Yes"<<endl;
        	}
        	else
        	{
        		cout<<"No"<<endl;
        	}
        	return 0;
        }
    • luogu P3275 [SCOI2011] 糖果

      • 容易发现,单源最长路求正环时,边权只有 \(0\) 和 \(1\) 。故若图中存在环,则这个环边权之和必须等于 \(0\) ,否则一定无解。

      • 类似 luogu P3008 [USACO11JAN] Roads and Planes G ,我们对原图进行缩点。然后进行拓扑求最长路即可。

        点击查看代码

        cpp 复制代码
        struct node
        {
        	ll nxt,to,w;
        }e[300000];
        ll head[300000],dis[300000],dfn[300000],low[300000],ins[300000],scc[300000],c[300000],u[300000],v[300000],w[300000],din[300000],cnt=0,m=0,tot=0,ans=0;
        stack<ll>s;
        void add1(ll u,ll v,ll w)
        {
        	cnt++;
        	e[cnt].nxt=head[u];
        	e[cnt].to=v;
        	e[cnt].w=w;
        	head[u]=cnt;
        }
        void add2(ll uu,ll vv,ll ww)
        {
        	m++;
        	u[m]=uu;
        	v[m]=vv;
        	w[m]=ww;
        }
        void tarjan(ll x)
        {
        	int i,k=0;
        	tot++;
        	dfn[x]=low[x]=tot;
        	ins[x]=1;
        	s.push(x);
        	for(i=head[x];i!=0;i=e[i].nxt)
        	{
        		if(dfn[e[i].to]==0)
        		{
        			tarjan(e[i].to);
        			low[x]=min(low[x],low[e[i].to]);
        		}
        		else
        		{
        			if(ins[e[i].to]==1)
        			{
        				low[x]=min(low[x],dfn[e[i].to]);
        			}
        		}
        	}
        	if(dfn[x]==low[x])
        	{
        		ans++;
        		while(x!=k)
        		{
        			k=s.top();
        			ins[k]=0;
        			c[k]=ans;
        			scc[ans]++;
        			s.pop();
        		}
        	}
        }
        void top_sort(ll n)
        {
        	queue<ll>q;
        	ll i,x;
        	for(i=1;i<=n;i++)
        	{
        		if(din[i]==0)
        		{
        			q.push(i);
        			dis[i]=1;
        		}
        	}
        	while(q.empty()==0)
        	{
        		x=q.front();
        		q.pop();
        		for(i=head[x];i!=0;i=e[i].nxt)
        		{
        			dis[e[i].to]=max(dis[e[i].to],dis[x]+e[i].w);
        			din[e[i].to]--;
        			if(din[e[i].to]==0)
        			{
        				q.push(e[i].to);
        			}
        		}
        	}
        }
        int main()
        {
        	ll n,q,uu,vv,pd,sum=-1,flag=0,i;//因为额外多算了n+1,所以事先需要设为-1
        	cin>>n>>q;
        	for(i=1;i<=n;i++)
        	{
        		add1(n+1,i,0);
        		add2(n+1,i,0);
        	}
        	for(i=1;i<=q;i++)
        	{
        		cin>>pd>>uu>>vv;
        		switch(pd)
        		{
        			case 1:
        				add1(uu,vv,0);
        				add2(uu,vv,0);
        				add1(vv,uu,0);
        				add2(vv,uu,0);
        				break;
        			case 2:
        				add1(uu,vv,1);
        				add2(uu,vv,1);
        				break;
        			case 3:
        				add1(vv,uu,0);
        				add2(vv,uu,0);
        				break;
        			case 4:
        				add1(vv,uu,1);
        				add2(vv,uu,1);
        				break;
        			case 5:
        				add1(uu,vv,0);
        				add2(uu,vv,0);
        				break;
        		}
        	}
        	for(i=1;i<=n+1;i++)
        	{
        		if(dfn[i]==0)
        		{
        			tarjan(i);
        		}
        	}
        	cnt=0;
        	memset(e,0,sizeof(e));
        	memset(head,0,sizeof(dis));
        	for(i=1;i<=m;i++)
        	{
        		if(c[u[i]]!=c[v[i]])
        		{
        			add1(c[u[i]],c[v[i]],w[i]);
        			din[c[v[i]]]++;
        		}
        		else
        		{
        			if(w[i]==1)
        			{
        				flag=1;
        				break;
        			}
        		}
        	}
        	if(flag==0)
        	{
        		top_sort(ans);
        		for(i=1;i<=ans;i++)
        		{
        			sum+=dis[i]*scc[i];
        		}
        	}
        	cout<<sum<<endl;
        	return 0;
        }
    • luogu P4878 [USACO05DEC] Layout G

      • 设 \(x_{i}\) 表示 \(0 \sim i\) 中奶牛的数量。

      • 由于一个点可以有多头奶牛,隐含着 \(x_{i+1}-x_{i} \ge 0\) 的关系。

      • 无法排队即建出的图中存在负环(需要从超级源点开始遍历)。

      • 可以任意远离即 \(1\) 无法到达 \(n\) 。

        点击查看代码

        cpp 复制代码
        struct node
        {
        	int nxt,to,w;
        }e[100000];
        int head[100000],dis[100000],vis[100000],num[100000],cnt=0;
        void add(int u,int v,int w)
        {
        	cnt++;
        	e[cnt].nxt=head[u];
        	e[cnt].to=v;
        	e[cnt].w=w;
        	head[u]=cnt;
        }
        void spfa(int s,int n)
        {
        	memset(dis,0x3f,sizeof(dis));
        	memset(vis,0,sizeof(vis));
        	memset(num,0,sizeof(num));
        	int x,i;
        	queue<int>q;
        	q.push(s);
        	dis[s]=0;
        	vis[s]=1;
        	while(q.empty()==0)
        	{
        		x=q.front();
        		vis[x]=0;
        		q.pop();
        		for(i=head[x];i!=0;i=e[i].nxt)
        		{
        			if(dis[e[i].to]>dis[x]+e[i].w)
        			{
        				dis[e[i].to]=dis[x]+e[i].w;
        				num[e[i].to]=num[x]+1;
        				if(num[e[i].to]>=n+1)
        				{
        					cout<<"-1"<<endl;
        					exit(0);
        				}
        				if(vis[e[i].to]==0)
        				{
        					q.push(e[i].to);
        					vis[e[i].to]=1;
        				}
        			}
        		}
        	}
        }
        int main()
        {
        	int n,m1,m2,i,u,v,w;
        	cin>>n>>m1>>m2;
        	for(i=1;i<=m1;i++)
        	{
        		cin>>u>>v>>w;
        		add(u,v,w);
        	}
        	for(i=1;i<=m2;i++)
        	{
        		cin>>u>>v>>w;
        		add(v,u,-w);
        	}
        	for(i=1;i<=n;i++)
        	{
        		add(0,i,0);
        	}
        	for(i=1;i<=n-1;i++)
        	{
        		add(i+1,i,-0);
        	}
        	spfa(0,n);
        	spfa(1,n);
        	if(dis[n]==0x3f3f3f3f)
        	{
        		cout<<"-2"<<endl;
        	}
        	else
        	{
        		cout<<dis[n]<<endl;
        	}
        	return 0;
        }
    • luogu P1645 序列

      • 多倍经验: luogu P1250 种树 | luogu P1986 元旦晚会 | SP116 INTERVAL - Intervals | UVA1723 Intervals

      • 设 \(x_{i}\) 表示 \(0 \sim i\) 中选择的数的个数。

      • \([a_{i},b_{i}]\) 中至少有 \(c_{i}\) 个数被选出等价于 \(x_{b_{i}+1}-x_{a_{i}} \ge c_{i}\) 。

      • 由于一个数只能选一次,隐含着 \(0 \le x_{i}-x_{i-1} \le 1\) 的关系。

        点击查看代码

        cpp 复制代码
        struct node
        {
        	int nxt,to,w;
        }e[200001];
        int head[200001],vis[200001],dis[200001],cnt=0;
        void add(int u,int v,int w)
        {
        	cnt++;
        	e[cnt].nxt=head[u];
        	e[cnt].to=v;
        	e[cnt].w=w;
        	head[u]=cnt;
        }
        void spfa(int s)
        {
        	int i,x;
        	memset(vis,0,sizeof(vis));
        	memset(dis,-0x3f,sizeof(dis));
        	queue<int>q;
        	q.push(s);
        	dis[s]=0;
        	vis[s]=1;
        	while(q.empty()==0)
        	{
        		x=q.front();
        		vis[x]=0;
        		q.pop();
        		for(i=head[x];i!=0;i=e[i].nxt)
        		{
        			if(dis[e[i].to]<dis[x]+e[i].w)
        			{
        				dis[e[i].to]=dis[x]+e[i].w;
        				if(vis[e[i].to]==0)
        				{
        					q.push(e[i].to);
        					vis[e[i].to]=1;
        				}
        			}
        		}
        	}
        }
        int main()
        {
        	int m,n=0,u,v,w,i,t,j;
        	cin>>m;
        	for(i=1;i<=m;i++)
        	{
        		cin>>u>>v>>w;
        		add(u,v+1,w);
        		n=max(n,v+1);
        	}
        	for(i=1;i<=n;i++)
        	{
        		add(i-1,i,0);
        		add(i,i-1,-1);
        	}
        	spfa(0);
        	cout<<dis[n]<<endl;
        	return 0;
        }
    • LibreOJ 10088. 「一本通 3.4 例 2」出纳员问题

      • 为方便代码书写,规定时间为 \(1 \sim 24\) 。

      • 设 \(sum_{i}\) 表示应聘的人中从第 \(i\) 个小时开始工作的人的数量, \(x_{i}\) 表示招聘的人中从第 \(i\) 个小时开始工作的人的数量。

      • 由题,有 \(\begin{cases} \sum\limits_{j=1}^{i}x_{j}+\sum\limits_{17+i}^{24}x_{j} \ge r_{i} & 1 \le i \le 7 \\ \sum\limits_{j=i-7}^{i}x_{j} \ge r_{i} & 8 \le i \le 24 \end{cases}\) ,即 \(\begin{cases} \sum\limits_{j=1}^{i}x_{j}+\sum\limits_{j=1}^{24}x_{j}-\sum\limits_{j=1}^{16+i}x_{j} \ge r_{i} & 1 \le i \le 7 \\ \sum\limits_{j=1}^{i}x_{j}-\sum\limits_{j=1}^{i-8}x_{j} \ge r_{i} & 8 \le i \le 24 \end{cases}\) ,移项得 \(\begin{cases} \sum\limits_{j=1}^{i}x_{j}-\sum\limits_{j=1}^{16+i}x_{j} \ge r_{i}-\sum\limits_{j=1}^{24}x_{j} & 1 \le i \le 7 \\ \sum\limits_{j=1}^{i}x_{j}-\sum\limits_{j=1}^{i-8}x_{j} \ge r_{i} & 8 \le i \le 24 \end{cases}\) 。

      • 由于一个数只能选一次,隐含着 \(0 \le x_{i}-x_{i-1} \le sum_{i}\) ,即 \(0 \le \sum\limits_{j=1}^{i}x_{j}-\sum\limits_{j=1}^{i-1}x_{j} \le sum_{i}\) 。

      • 枚举 \(\sum\limits_{i=1}^{24}x_{i}\) 判断即可。

        点击查看代码

        cpp 复制代码
        struct node
        {
        	int nxt,to,w;
        }e[100];
        int head[100],dis[100],vis[100],num[100],r[100],sum[100],cnt=0;
        void add(int u,int v,int w)
        {
        	cnt++;
        	e[cnt].nxt=head[u];
        	e[cnt].to=v;
        	e[cnt].w=w;
        	head[u]=cnt;
        }
        bool spfa(int s,int n)
        {
        	memset(dis,-0x3f,sizeof(dis));
        	memset(vis,0,sizeof(vis));
        	memset(num,0,sizeof(num));
        	int x,i;
        	queue<int>q;
        	q.push(s);
        	dis[s]=0;
        	vis[s]=1;
        	while(q.empty()==0)
        	{
        		x=q.front();
        		vis[x]=0;
        		q.pop();
        		for(i=head[x];i!=0;i=e[i].nxt)
        		{
        			if(dis[e[i].to]<dis[x]+e[i].w)
        			{
        				dis[e[i].to]=dis[x]+e[i].w;
        				num[e[i].to]=num[x]+1;
        				if(num[e[i].to]>=n+1)
        				{
        					return false;
        				}
        				if(vis[e[i].to]==0)
        				{
        					q.push(e[i].to);
        					vis[e[i].to]=1;
        				}
        			}
        		}
        	}
        	return true;
        }
        int main()
        {
        	int t,n,x,ans,i,j,k;
        	cin>>t;
        	for(k=1;k<=t;k++)
        	{   
        		memset(sum,0,sizeof(sum));
        		ans=-1;
        		for(i=1;i<=24;i++)
        		{
        			cin>>r[i];
        		}
        		cin>>n;
        		for(i=1;i<=n;i++)
        		{
        			cin>>x;
        			sum[x+1]++;
        		}
        		for(i=0;i<=n;i++)
        		{
        			cnt=0;
        			memset(e,0,sizeof(e));
        			memset(head,0,sizeof(head));
        			add(0,24,i);
        			add(24,0,-i);
        			for(j=1;j<=24;j++)
        			{
        				add(j-1,j,0);
        				add(j,j-1,-sum[j]);
        			}
        			for(j=1;j<=8;j++)
        			{
        				add(j+16,j,r[j]-i);
        			}
        			for(j=9;j<=24;j++)
        			{
        				add(j-8,j,r[j]);
        			}
        			if(spfa(0,24)==true)
        			{
        				ans=i;
        				break;
        			}
        		}
        		if(ans!=-1)
        		{
        			cout<<ans<<endl;
        		}
        		else
        		{
        			cout<<"No Solution"<<endl;
        		}
        	}
        	return 0;
        }

参考资料

《算法竞赛进阶指南》------李煜东

OI-WiKi

我的无优化 SPFA 果然有问题

相关推荐
WangLi&a10 个月前
差分约束算法
图论·线性规划·最短路径·差分约束·差额限制
viclao1 年前
差分约束建边方法
算法·差分约束