D119 最短路→差分约束 SPFA 算法 P10941 [ICPC 2000 Tehran R] Cashier Employment 雇佣收银员

D119 最短路→差分约束 SPFA 算法 P10941 [ICPC 2000 Tehran R] Cashier Employment 雇佣收银员_哔哩哔哩_bilibili

 

P10941 [ICPC 2000 Tehran R] Cashier Employment - 洛谷

求满足约束条件的最小解。

思路

$r[i]$ 表示 $i$ 时刻需要的人数,$num[i]$ 表示 $i$ 时刻来的人数,$x[i]$ 表示 $i$ 时刻雇佣的人数

1. $i$ 时刻雇佣的人数应小于等于来的人数:$0\le x[i]\le num[i]$

2. 从 $i-7$ 到 $i$ 时刻雇佣的人都能在 $i$ 时刻工作,即 $x[i-7]+x[i-6]+...+x[i]\ge r[i]$

使用前缀和 $s[i]=x[1]+x[2]+...+x[i]$

1. $i$ 时刻雇佣的人数应小于等于来的人数:$0\le s[i]-s[i-1] \le num[i]$

2. 当 $8\le i\le 24$ 时,$s[i]-s[i-8]\ge r[i]$
    当 $1\le i\le 7$ 时,$s[i]+s[24]-s[i+16]\ge r[i]$,如 $i=2, x[19]+...+x[24]+x[1]+x[2]\ge r[2]$

因为求最小解,约束条件均转化为 $\ge$
1. $s[i]-s[i-1]\ge 0,1\le i\le 24$
2. $s[i-1]-s[i]\ge -num[i],1\le i\le 24$
3. $s[i]-s[i-8]\ge r[i],8\le i\le 24$
4. $s[i]-s[i+16]\ge r[i]-s[24],1\le i\le 7$

约束关系 4 中有 $s[24]$ 这个变量,其实 $s[24]=x[1]+x[2]+...+x[24]$ 就是待求的答案

我们可以二分答案,二分一个 $mid$,$s[i]-s[i+16]\ge r[i]-mid,1\le i\le 7$

$s[24]=mid$ 转化成 $mid\le s[24]\le mid$

即 $s[24]-s[0]\ge mid$ 且 $s[0]-s[24]\ge -mid,s[0]=0$

我们把变量 $s[i]$ 看做点,点的编号即下标 $i$,连边建图

从第一个 add 可知,0 点就是超级源点(0 边权到达各点),所以从 0 点出发跑最长路

image

当 $mid\ge ans$ 时,一定满足要求,无正环;当 $mid<ans$ 时,一定不满足要求,有正环

点数 $N\le 25$,边数 $M\le 25∗3=75$,所以时间复杂度 $O(T*1000*N*M)$

相关板子:

D116【模板】最短路→差分约束 SPFA 算法 P5960 差分约束 - 董晓 - 博客园

 

// 差分约束 SPFA 算法 O(NM*logN)
#include<bits/stdc++.h>
using namespace std;

const int N=25,M=100;
int h[N],to[M],ww[M],ne[M],idx;
void add(int a,int b,int c){
  to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx;
}
int n;
int r[N],num[N];
int d[N],cnt[N],vis[N];

void build(int mid){
  idx=0; memset(h,0,sizeof h);
  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]-mid);
  add(0,24,mid); add(24,0,-mid);
}
bool spfa(int mid){
  build(mid); //建图
  memset(d,-0x3f,sizeof d); d[0]=0;
  memset(cnt,0,sizeof cnt);
  memset(vis,0,sizeof vis);
  queue<int> q; 
  q.push(0);vis[0]=true; //0就是超级源点
  
  while(!q.empty()){
    int u=q.front(); q.pop();vis[u]=false;
    for(int i=h[u]; i; i=ne[i]){
      int v=to[i];
      if(d[v]<d[u]+ww[i]){
        d[v]=d[u]+ww[i]; //最长路
        cnt[v]=cnt[u]+1; //走过的边数
        if(cnt[v]>=25) return 1; //有正环
        if(!vis[v]) q.push(v),vis[v]=true;
      }
    }
  }
  return 0; //无正环
}
int main(){
  int T; cin>>T;
  while(T--){
    for(int i=1; i<=24; i++)cin>>r[i]; //每个时刻的需要人数
    cin>>n;
    memset(num,0,sizeof num);
    for(int i=1,t; i<=n; i++){
      cin>>t;
      num[t+1]++; //每个时刻的应聘人数
    }
    int l=-1,r=n+1;
    while(l+1<r){
      int mid=l+r>>1;
      if(!spfa(mid)) r=mid; //如果无正环,说明人多
      else l=mid;
    }
    if(r==n+1) cout<<"No Solution\n";
    else cout<<r<<"\n";
  }
}

 

posted @ 2026-03-30 19:32  董晓  阅读(92)  评论(0)    收藏  举报