BZOJ2330 糖果[差分约束方案+spfa?/tarjan]

以往对于差分约束理解不是太深,导致这题屡次被坑,在此记录一下细节的理解。


差分约束实际上就是利用了spfa的一个特性:只要有$dis_y>dis_x+w_{x,y}$就松弛,直到所有边关系都满足$dis_yle dis_x+w_{x,y}$,而这一不等式恰好可以套在差分约束问题里。差分约束要求满足前面这个玩意,把每个变量的值看做$dis_i$,建边,跑spfa最短路,使得所有边都满足关系。这个之前就已经明白了。

但是忽视了一点:注意到跑出来的最短路中$dis$即为$x$的一组合法取值,并且这组取值同时加上某个值$a$,也是满足不等式约束的。不过,这些解集构成的集合,并不是解集集合的全集(有点绕),显然在跑spfa时只是使得每个点的$dis$贴着松弛他的$dis+w$,其实这个实际取值$x$还可以更小。而如果用最长路的建边方式,$x$可以更大。两者都是等效的。在这题里面,由于要求每个数都是正整数且总和最小,并不可以使用建负权边跑最短路的方法。。。因为解出来的解集即使全部平移到正数区域,你也不知道有没有哪些数可以更小一些,哪些数不能再小了。而建正权边跑最长路,根据上述分析,每个$x$也就是$dis$必然是贴着最小的可能取值的,这样就极其简便的获取最小解。

第二个点,因为spfa理论复杂度$O(mn)$,所以想卡很容易,再加上这题由于差分约束要把所有点都加入队列,所以很容易想到如果构造一条链,连边全部反过来,$1leftarrow 2leftarrow ...leftarrow n$,显然会跑接近$n^2$次,不过数据里并没有故意卡,倒是有一个顺着正向的链的data,我这里采用直接顺着加入队列就没有问题了。但是实际上还是可以卡的?另外,有的点明显spfa被卡,但是有数据连了-1的自环,可以提前特判结束。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 #include<queue>
 7 #define dbg(x) cerr << #x << " = " << x <<endl
 8 #define dbg2(x,y) cerr<< #x <<" = "<< x <<"  "<< #y <<" = "<< y <<endl
 9 using namespace std;
10 typedef long long ll;
11 typedef double db;
12 typedef pair<int,int> pii;
13 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
14 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
15 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;}
16 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;}
17 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
18 template<typename T>inline T read(T&x){
19     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
20     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
21 }
22 const int N=1e5+7;
23 struct thxorz{int to,nxt,w;}G[N<<1];
24 int Head[N],tot,inq[N],rel[N];
25 int n,m;
26 ll ans;
27 inline void Addedge(int x,int y,int z){G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot,G[tot].w=z;}
28 queue<int> q;
29 ll dis[N];
30 #define y G[j].to
31 inline bool spfa(){
32     for(register int i=1;i<=n;++i)q.push(i),inq[i]=1,dis[i]=1;
33     while(!q.empty()){
34         int x=q.front();q.pop();inq[x]=0;//dbg2(x,dis[x]);
35         for(register int j=Head[x];j;j=G[j].nxt)if(MAX(dis[y],dis[x]+G[j].w)){
36             rel[y]=rel[x]+1;if(rel[y]>=n)return 0;
37             if(!inq[y])inq[y]=1,q.push(y);
38         }
39     }
40     return 1;
41 }
42 #undef y
43 int main(){//freopen("1.in","r",stdin);//freopen("test.ans","w",stdout);
44     read(n),read(m);
45     for(register int i=1,opt,x,y;i<=m;++i){
46         read(opt),read(x),read(y);
47         if(opt==1)Addedge(x,y,0),Addedge(y,x,0);
48         else if(opt==2)Addedge(x,y,1);
49         else if(opt==3)Addedge(y,x,0);
50         else if(opt==4)Addedge(y,x,1);
51         else Addedge(x,y,0);
52         if((opt==2||opt==4)&&x==y){printf("-1
");return 0;}
53     }
54     if(spfa()){
55         for(register int i=1;i<=n;++i)ans+=dis[i];
56         printf("%lld
",ans);
57     }
58     else puts("-1");
59     return 0;
60 }
View Code

2019.11.01UPD:

关于正权图或者负权图的差分约束有更快的线性做法。

以最长路为例,如果图上只有正权边和零边,那么,只要看有没有环是正环,也就是说,对于每一条正边,如果他在一个SCC里,那显然就是正环了,直接判无解。否则,有环的话也只能是零环,零环意味着环上所有点值相同(因为一个被更新,所有点最长路顺着一圈都被更新),所以可以把他们缩掉,这样就变成了一张DAG,这时候就可以直接拓扑排序更新最大值了,最后每个点所在SCC的值就是他的解了。复杂度线性。当然,环上有正有负就不好整了。spfa又死了。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 #include<queue>
 7 #define dbg(x) cerr << #x << " = " << x <<endl
 8 #define dbg2(x,y) cerr<< #x <<" = "<< x <<"  "<< #y <<" = "<< y <<endl
 9 using namespace std;
10 typedef long long ll;
11 typedef double db;
12 typedef pair<int,int> pii;
13 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
14 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
15 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;}
16 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;}
17 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
18 template<typename T>inline T read(T&x){
19     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
20     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
21 }
22 const int N=1e5+7;
23 int n,m;
24 ll ans;
25 struct thxorz{
26     int head[N],nxt[N<<1],to[N<<1],from[N<<1],w[N<<1],tot;
27     inline void add(int x,int y,int z){to[++tot]=y,from[tot]=x,nxt[tot]=head[x],head[x]=tot,w[tot]=z;}
28 }G1,G2;
29 int dfn[N],low[N],stk[N],instk[N],bel[N],scc,tim,top;
30 #define y G1.to[j]
31 void tarjan(int x){
32     dfn[x]=low[x]=++tim,stk[++top]=x,instk[x]=1;
33     for(register int j=G1.head[x];j;j=G1.nxt[j]){
34         if(!dfn[y])tarjan(y),MIN(low[x],low[y]);
35         else if(instk[y])MIN(low[x],dfn[y]);
36     }
37     if(low[x]==dfn[x]){
38         int tmp;++scc;
39         do instk[tmp=stk[top--]]=0,bel[tmp]=scc;while(tmp^x);
40     }
41 }
42 #undef y
43 queue<int> q;
44 int dis[N],deg[N];
45 #define y G2.to[j]
46 inline void topo(){
47     for(register int i=1;i<=scc;++i){dis[i]=1;if(!deg[i])q.push(i);}
48     while(!q.empty()){
49         int x=q.front();q.pop();
50         for(register int j=G2.head[x];j;j=G2.nxt[j]){
51             MAX(dis[y],dis[x]+G2.w[j]);
52             if(!(--deg[y]))q.push(y);
53         }
54     }
55 }
56 #undef y
57 int main(){//freopen("1.in","r",stdin);//freopen("test.ans","w",stdout);
58     read(n),read(m);
59     for(register int i=1,opt,x,y;i<=m;++i){
60         read(opt),read(x),read(y);
61         if(opt==1)G1.add(x,y,0),G1.add(y,x,0);
62         else if(opt==2)G1.add(x,y,1);
63         else if(opt==3)G1.add(y,x,0);
64         else if(opt==4)G1.add(y,x,1);
65         else G1.add(x,y,0);
66     }
67     for(register int i=1;i<=n;++i)if(!dfn[i])tarjan(i);
68     for(register int t=1;t<=G1.tot;++t){
69         int x=G1.from[t],y=G1.to[t];
70         if(bel[x]==bel[y]&&G1.w[t]){puts("-1");return 0;}
71         else if(bel[x]^bel[y])G2.add(bel[x],bel[y],G1.w[t]),++deg[bel[y]];
72     }
73     topo();
74     for(register int i=1;i<=n;++i)ans+=dis[bel[i]];
75     printf("%lld
",ans);
76     return 0;
77 }
tarjan!
原文地址:https://www.cnblogs.com/saigyouji-yuyuko/p/11637241.html