0
点赞
收藏
分享

微信扫一扫

差分约束整理

40dba2f2a596 2022-04-04 阅读 20
c++

非常早之前整理的差分约束,做题用到,发现都忘了

差分约束

如果一个系统由 nn 个变量 a1,a2,,anma_1,a_2,\cdots,a_n和m 个约束条件组成,每个约束条件均为形如 aiajka_i-a_j \leq k的不等式 (i,j[1,n],j[1,n]ki,j \in [1,n],j∈[1,n],k为常数),则称其为差分约束系统

(1)求不等数组的可行解

​ 源点需要满足的条件:从源点出发,一定可以走到所有的边,(不是一定要走到所有的点,因为对于孤立的点来说点的取值是不影响不等式是否成立的)

​ 步骤:

  1. 先将每个不等式 xixj+Ckx_i \leq x_j + C_k 转化成一条从 xjxiCkx_j 走到 x_i长度为C_k的一条边

  2. 找到一个超级源点,使得该源点一定可以遍历到所有的边

  3. 从源点求一遍单源最短路

    ​ 结果1:如果存在负环,则原不等式组一定无解(通过数学推导得到)

    ​ 结果2:如果没有负环,则dist数组就是原不等式组的一个可行解。

(2)求最大最小值(这里的最大值最小值指的是每个变量的最值)

​ 结论: 如果求得是最小值,应该求最长路;如果求得是最大值,应该求最短路

​ 问题: 如何转化 xic,c?x_i \leq c,c是一个常数 ?

​ 方法:建立一个超级源点, 0 号节点,然后建立从 0 -> i 的边 , 长度是 c 的边即可.

​ 以求xix_i 的最大值为例,求所有从xi 出发,构成的不等式链

xixj+C1xk+C2+C1...x0(x0=0)+C1+C2+...x_i \leq x_j + C_1 \leq x_k + C_2 + C_1 \leq ... \leq x_0 (x_0=0) + C_1+C_2+...

​ 所计算出的上界,最终的xix_i 是所有上界的最小值.


做题实录

雇佣收银员 差分约束+枚举

尝试使用前缀和的思想去构造不等式组,不清楚怎么表达8小时工作关系。

看了mrk和y总视频讲解了搞懂了这题(超级不错)

num[i]ix[i]is[i]x[i]num[i]表示i时刻申请人数,x[i]表示i时刻实际上岗人数,s[i]是x[i]的前缀和数组。
0xinum[i],i[1,24]sisi8ri,i[8,24]sisi+16+s24ri,i[1,7] 0 \leq x_i \leq num[i] ,i\in [1,24]\\ s_i - s_{i-8} \geq r_i , i \in [8,24]\\ s_i - s_{i+16} + s24 \geq r_i , i \in [1,7]\\
s将上述公式转化成用s数组表示的形式
sisi1+0,i[1,24]si1sinum[i],i[1,24]sisi8+ri,i[8,24]si+s24si+16ri,i[1,7] s_i \geq s_{i-1}+0 , i\in [1,24] \\ s_{i-1} \geq s_i - num[i] , i\in [1,24]\\ s_i \geq s_{i-8} + r_i , i \in [8,24] \\ s_i + s_{24} - s_{i+16} \geq r_i , i \in [1,7]

观察到 0 号点可以作为超级源点,所以一共有25个点,s24s_{24}就是题目的答案,可以通过枚举s24s_{24}的方法,每次枚举都建立新图,跑spfa 。
特别需要注意的一点s24s_{24}是定值,所以 S24==C==S0+CS_{24} == C == S_0 + C
s0s24C   add(24,0,s24)s24s0+C   add(0,24,s24) s_0 \geq s_{24} - C \ \ \ add(24,0,-s24)\\ s_{24} \geq s_{0} + C \ \ \ add(0,24,s24)

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");
}
举报

相关推荐

0 条评论