差分约束
差分约束是将约束条件转化为图论中最短路问题的一类方法,也可以说是思想。
P5960 【模板】差分约束
差分约束最一般的形式就是给定一些这样的条件,然后要求构造一组可行解。
我们发现移项之后其形式等价于最短路中的三角形不等式。于是一条形如
类似于
于是我们在图中建一条 \(c'_1\to c_1\) 的边,边权值为 \(y_1\) 即可。
但是这样我们只知道所有点之间的相对大小关系。如果要划定一组解的话就需要给定一个天然的上界或者下界。
一般我们是建一个超级源点 \(S\),建立 \(\forall i,S\to i\),边权为 0。这个东西代表我们划定所有东西的天然上界是 0,一些没有被其他点约束的点值就自然被赋值为 0。
于是直接照着跑即可。注意上面式子的形式是最短路的形式,于是我们跑最短路。如果比较闲当然也可以改成跑最长路的形式。
注意边权有正有负所以跑 spfa。
而对于无解的情况,我们直接在跑 spfa 的同时判断是否有负环即可。负环的意义就类似于最后跑出来一个连着的互相矛盾的式子。
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+7,inf=1e9+7;
#define pii pair <int,int>
#define mp make_pair
int n,m,dis[N],tot[N],vis[N];
vector <pii> q[N];
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;for(int i=1,u,v,w;i<=m;i++)cin>>v>>u>>w,q[u].push_back(mp(v,w));
for(int i=1;i<=n;i++)dis[i]=inf,q[0].push_back(mp(i,0));
queue <int> t;t.push(0);dis[0]=0;
while(!t.empty()){
int u=t.front();t.pop();vis[u]=0;if(tot[u]>=n+1){cout<<"NO\n";return 0;}
for(pii tmp:q[u]){
int v=tmp.first,w=tmp.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;tot[v]=tot[u]+1;
if(vis[v])continue;
vis[v]=1;t.push(v);
}
}
}
for(int i=1;i<=n;i++)cout<<dis[i]<<' ';return 0;
}
P1993 小 K 的农场
这道题相当于多了一种限制:两者相等。
我们有常见的处理形式就是将
拆成
二者。于是我们让相等的点互相连边权为 0 的边即可。
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+7;
#define pii pair <int,int>
#define mp make_pair
int dis[N],cnt[N],n,m,vis[N],tot[N];
vector <pii> q[N];
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;for(int i=1;i<=m;i++){
int op,a,b,c;cin>>op>>a>>b;
if(op==1)cin>>c,q[a].push_back(mp(b,-c));
if(op==2)cin>>c,q[b].push_back(mp(a,c));
if(op==3)q[a].push_back(mp(b,0)),q[b].push_back(mp(a,0));
}
for(int i=1;i<=n;i++)q[0].push_back(mp(i,0));
queue <int> t;memset(dis,0x3f3f,sizeof(dis));t.push(0);dis[0]=0;
while(!t.empty()){
int u=t.front();t.pop();vis[u]=0;if(tot[u]>=n+1){cout<<"No\n";return 0;}
for(pii tmp:q[u]){
int v=tmp.first,w=tmp.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;tot[v]=tot[u]+1;
if(vis[v])continue;
vis[v]=1;t.push(v);
}
}
}
cout<<"Yes\n";return 0;
}
P4926 [1007] 倍杀测量者
这道题就比较灵活了。
首先显然二分答案,设二分的东西为 \(T\)。然后考虑如何去 check。由于要求的是无论赋值都至少会出现一个人女装,于是考虑将图构造成不女装的形式。例如“分数不高于另一个人的 \(k\) 倍就女装”,限制就是“分数高于另一个人的 \(k\) 倍”。这样限制的好处是只要约束无解,那么就一定会有人女装。
考虑限制的形式。考虑前两个限制的对称性我们只考虑第一种:
发现与最经典的形式不太一样,考虑转化成一次的形式。发现有
于是考虑转化一下。设 \(dis'_u=\log dis_u\)。这样形式就比较好看了
发现全是定值。于是就可以差分约束了。
然后发现还有第三种限制,也就是
这个东西怎么做?考虑真正让 \(dis_u\) 恰好等于 \(w\) 可能是难办的,因为我们还要给每个点划定一个自然的上界。
但是我们发现,对于差分约束的方案,在求最短路的情况下,保证的是点之间的相对值不变,于是我们只需要保证这个就可以了。
于是我们建一个虚空的节点,然后将其拆成 \(\ge \log w\) 和 \(\le \log w\) 两条限制,与这个虚空节点连边即可。
这个虚空节点保证了对于所有赋值已经被确定了的点的相对值是不变的。
同样设定一个自然上界即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ld double
const ld eps=1e-5,inf=1e12;const int N=5005;
bool dcmp(ld x){return x<-eps?-1:(x>eps?1:0);}
int n,m,s,vis[N],tot[N];ld dis[N];
struct node{int v;ld w;int t;};
vector <node> q[N];
bool check(ld T){
queue <int> t;for(int i=1;i<=n+1;i++)dis[i]=inf,tot[i]=vis[i]=0;dis[0]=0,tot[0]=1;t.push(0);//注意一定要清空 vis!!!!
while(!t.empty()){
int u=t.front();t.pop();vis[u]=0;
if(tot[u]>=n+2)return 1;
for(node tmp:q[u]){
int v=tmp.v,type=tmp.t;ld w=tmp.w;
if(type==1)w=-log2(w-T);
if(type==2)w=log2(w+T);
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;tot[v]=tot[u]+1;
if(vis[v])continue;
vis[v]=1,t.push(v);
}
}
}
return 0;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m>>s;
for(int i=1,op,a,b;i<=m;i++){ld w;cin>>op>>a>>b>>w;q[a].push_back({b,w,op});}
for(int i=1,x,w;i<=s;i++)cin>>x>>w,q[n+1].push_back({x,log2(w),3}),q[x].push_back({n+1,-log2(w),3});
for(int i=1;i<=n;i++)q[0].push_back({i,0,3});
ld l=0,r=10,ans=l;
if(!check(0)){cout<<"-1\n";return 0;}
while(dcmp(r-l)>0){
ld mid=(r+l)/2.0;
if(check(mid))ans=mid,l=mid+eps;
else r=mid-eps;
}
cout<<fixed<<setprecision(5)<<ans;return 0;
}
AT_agc056_c [AGC056C] 01 Balanced
经典套路是将 0 看成 -1,1 看成 1,然后限制就是 \(l-1\) 与 \(r\) 的前缀和是相同的,于是限制转变成了很多相等的限制,于是连一些边权为 0 的双向边即可。
然后还有一个隐含的限制是相邻的前缀和之差的绝对值为 1。于是考虑在相邻的两个点之间互相连边权为 1 的双向边,这个东西表示了
但是没有限制到相邻的点的权值恰好相等的情况。下面来证明这样连边一定不会有相邻的位置前缀和的值相等的情况。
我们将点按位置的奇偶分为两类。发现上面第一种边权为 0 的边一定是在奇偶性相同的点之间连的。然后边权为 1 的边是在相邻的点之间连的。
于是我们可以直观的看到,位置奇偶不同,二者的前缀和的奇偶性一定也不同,于是我们证明了相邻两点前缀和权值相同的情况,也就证明了这样连边的正确性。
于是如果不是紫题到这里就结束了。但是看一眼数据范围显然不太允许跑 spfa。瓶颈显然在最短路,考虑有没有什么方法优化一下最短路的过程。
发现边权只有 0/1,于是直接跑 01 bfs 即可。复杂度就做到了线性。
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7,inf=1e9+7;
#define pii pair <int,int>
#define mp make_pair
int n,m,dis[N],que[N<<2];
vector <pii> q[N];
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);//通过类似二分图的东西保证了相邻的边都是 +/- 1
cin>>n>>m;
for(int i=1;i<=m;i++){
int l,r;cin>>l>>r;l--;q[l].push_back(mp(r,0));q[r].push_back(mp(l,0));
}
for(int i=1;i<=n;i++)q[i].push_back(mp(i-1,1)),q[i-1].push_back(mp(i,1)),dis[i]=inf;
int l=n+1,r=n+1;que[l]=0;
while(l<=r){
int u=que[l];l++;
for(pii tmp:q[u]){
int v=tmp.first,w=tmp.second;
if(w==0&&dis[v]>dis[u])dis[v]=dis[u],que[--l]=v;
if(w==1&&dis[v]>dis[u]+1)dis[v]=dis[u]+1,que[++r]=v;
}
}
for(int i=n;i>=1;i--)dis[i]=dis[i]-dis[i-1];
for(int i=1;i<=n;i++)cout<<(dis[i]>0?0:1);return 0;
}

浙公网安备 33010602011771号