非常早之前整理的差分约束,做题用到,发现都忘了
差分约束
如果一个系统由 个变量 个约束条件组成,每个约束条件均为形如 的不等式 (),则称其为差分约束系统。
(1)求不等数组的可行解
源点需要满足的条件:从源点出发,一定可以走到所有的边,(不是一定要走到所有的点,因为对于孤立的点来说点的取值是不影响不等式是否成立的)
步骤:
-
先将每个不等式 转化成一条从
-
找到一个超级源点,使得该源点一定可以遍历到所有的边
-
从源点求一遍单源最短路
结果1:如果存在负环,则原不等式组一定无解(通过数学推导得到)
结果2:如果没有负环,则dist数组就是原不等式组的一个可行解。
(2)求最大最小值(这里的最大值最小值指的是每个变量的最值)
结论: 如果求得是最小值,应该求最长路;如果求得是最大值,应该求最短路
问题: 如何转化
方法:建立一个超级源点, 0 号节点,然后建立从 0 -> i 的边 , 长度是 c 的边即可.
以求 的最大值为例,求所有从xi 出发,构成的不等式链
所计算出的上界,最终的 是所有上界的最小值.
做题实录
雇佣收银员 差分约束+枚举
尝试使用前缀和的思想去构造不等式组,不清楚怎么表达8小时工作关系。
看了mrk和y总视频讲解了搞懂了这题(超级不错)
观察到 0 号点可以作为超级源点,所以一共有25个点,就是题目的答案,可以通过枚举的方法,每次枚举都建立新图,跑spfa 。
特别需要注意的一点,是定值,所以
const int N=30,M=1e9+7;
ll n,m,_;
int num[25],x[25],s[25],r[25];
//一共有25个点,给30个余额的,3*n的边
int h[N],e[N*3],ne[N*3],w[N*3],idx;
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dist[N],cnt[N];
bool st[N];
void build(int s24){
idx=0;
mem(h,-1);
for(int i=1;i<=24;i++){
add(i-1,i,0);
add(i,i-1,-num[i]);
}
for(int i=8;i<=24;i++){
add(i-8,i,r[i]);
}
for(int i=1;i<=7;i++){
add(i+16,i,r[i]-s24);
}
add(24,0,-s24);
add(0,24,s24);
}
bool spfa(int s24)
{
build(s24);//将建图内置到spfa中,tql
mem(dist,-0x3f);// 跑最长路
mem(st,0);
mem(cnt,0);
queue<int>que;
que.push(0);
st[0]=1;
dist[0]=0;
while(que.size()){
auto t=que.front();
que.pop();
st[t]=0;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dist[j]<dist[t]+w[i]){//求一个最长路
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=25)return 0;// 0是超级源点,25个点
if(!st[j]){
que.push(j);
st[j]=1;
}
}
}
}
return 1;
}
void solve()
{
for(int i=1;i<=24;i++){
cin>>r[i];
}
cin>>n;
mem(num,0);
for(int i=0;i<n;i++){
int x;cin>>x;
x++;//所有的点向后移动一位
num[x]++;
}
bool flag=false;
for(int i=0;i<=1000;i++){
if(spfa(i)){
cout<<dist[24]<<endl;
flag=true;
break;
}
}
if(!flag){
puts("No Solution");
}
}
int main()
{
cin>>_;
while(_--)
{
solve();
}
return 0;
}
还可以进行二分答案优化
bool flag=false;
int l=0,r=n;
while(l<r){
int mid=l+r>>1;
if(spfa(mid)){
r=mid;
}
else{
l = mid + 1;
}
}
if(spfa(r)){
cout<<dist[24]<<endl;
}else{
puts("No Solution");
}