P3275 [SCOI2011]糖果
原题链接
考察:差分约束+SPFA
引入:
什么是差分约束?
差分约束系统是一个n元一次不等式组.
它的作用包括:
(1) 求一组不等式的可行解
(2) 求一组不等式的最大值或最小值.(每个变量的最值)
不等式的格式是 : Xi \(\leq\) X j + C (C为常数)
这里联想到最短路问题,如果图中不存在负环,那么在求完最短路之后,对于每一条边 i ---> j,必然满足 :
dist[j] \(\leq\) dist[i] + road[i].w 注意到这个等式与差分约束的不等式很像,所以对于差分约束求可行解问题,我们都可以转化为单源最短路问题.
但是源点是不能任取的,我们要求从源点出发,一定能遍历到所有的边.那么怎样才能满足要求呢? 一般是建立虚拟源点
1.求可行解的步骤
(1) 将每个不等式转化为一条边.
(2) 找一个超级源点,使得该源点一定可以遍历到所有边.
(3) 从源点求单源最短路.
注意:如果图中存在负环,那么不等式一定矛盾,这两者等价
2.最长路与最短路的区别
求完所有点的最短路后, dist[j] \(\leq\) dist[i] + road[i].w . 所以 Xi \(\leq\) X j + C
与之对应求完所有点的最长路后, dist[j] \(\geq\) dist[i] + road[i].w . 对应 X i \(\geq\) X j + C
3.求最大、最小值
- 如果求最小值那么就是求最长路
- 如果求最大值那么就是求最短路
如果是求最值问题,一般都会有一个绝对值,即 : X i \(\leq\) C 或X \(\geq\) C,否则X的取值之间都是相对关系.如果X 1 ,X 2 ,X i ...是可行解,那么X+d也是可行解.
问题:如何转化X i \(\leq\) C为图论的一条边?
方法:建立虚拟源点X 0 , 设X 0 = 0 , 那么 X i \(\leq\) C ----> X i \(\leq\) X 0 + C .
X i \(\leq\) X 0 + C 适用于求最大值的情况
那么如何求最大值?
枚举所有从X i 出发的不等式链.
X i \(\leq\) X j + C j \(\leq\) Xk + C k \(\leq\) ... \(\leq\) X 0 +C j + C j +一堆常数
所有不等式链取最小值即X i 的最大值.
每一条不等式链,都是从0出发到达X i 的一条路径,根据上面的结论,X i 的最大值就是最短路径的长度.
思路:
将每个条件化为不等式,因为要取最小所以边值是1或0.然后套差分约束的板子.环的长度是n2 ,所以需要开long long
#include <iostream>
#include <queue>
#include <cstring>
#include <stack>
using namespace std;
typedef long long LL;
const int N = 100010;
int h[N],n,m,idx,cnt[N];
bool st[N];
LL dist[N];
struct Road{
int fr,to,ne,w;
}road[N*3];
void add(int a,int b,int w)
{
road[idx].w = w,road[idx].fr = a,road[idx].to = b,road[idx].ne = h[a],h[a] = idx++;
}
LL spfa()
{
for(int i=1;i<=n+1;i++) dist[i] = -1e14;
stack<int> q;
dist[0] = 0;
q.push(0);
st[0] = 1;
int count = 0;
while(q.size())
{
int u = q.top();
q.pop();
st[u] = 0;
for(int i=h[u];~i;i=road[i].ne)
{
int v = road[i].to;
if(dist[v]<dist[u]+road[i].w)
{
dist[v] = dist[u]+road[i].w;
cnt[v] = cnt[u]+1;
if(cnt[v]>=n+1) return -1;
if(!st[v]) q.push(v),st[v] = 1;
}
}
}
LL res = 0;
for(int i=1;i<=n;i++) res+=dist[i];
return res;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int x,a,b; scanf("%d%d%d",&x,&a,&b);
if(x==1) add(a,b,0),add(b,a,0);
else if(x==2) add(a,b,1);
else if(x==3) add(b,a,0);
else if(x==4) add(b,a,1);
else add(a,b,0);
}
for(int i=1;i<=n;i++) add(0,i,1);
printf("%lld\n",spfa());
return 0;
}