躺在前面
不得不说厉害的人真的很多,有些题不看题解全靠me自己做真的太难了。me还是不够强啊。
差分是个什么呢?
差分就是,题目给定一堆要求,这些要求在转化后可以表示成多个A - B ≥ val 或者A - B ≤ val 组合起来的不等式组
构成这个不等式组的不等式,一般左边是两个状态(两个待求值),右边是一个定值(一般是已知值),然后再通过模型转化,转化成最长路或者最短路问题求解
举个栗子
给定一个长度为N的序列
题目将会以一些三元组[a,b,c]对这个数列进行描述,表示序列中数值在[a,b]之间的数字至少有ci个
询问使得所有条件都成立的序列长度最短是多少。
这个破题,让me看看啊…
先要把题目条件表示成式子
定义S数组:S[i]表示序列中数值在0到i之间的数的个数。
定义maxN:maxN为所有b中最大的,即整个数列的最大数值
那么题目中的限制条件就可以被表示成
对不等式进行移项:
减一是因为[a,b]是闭区间,a也要包括在内。
而我们在跑最长路的时候,更新dist数组的条件是这样的
是不是有一点意思qwq
我们把这个S数组看成dist数组,把不等式看成边,一开始全部赋值为0,然后跑最长路。可以发现,本来不成立的不等式,在更新为最长路后,恰好满足了条件最苛刻的不等式,也就是取得了符合所有不等式的最小解。
回到题目上,把所有的约束条件列出来
将所有不等式全部变成B ≥ A + C的形式,按照从A连向B,边长为C的方式建图,跑最长路。这样跑出来的dist[S[maxN]]就是符合所有限制条件的最小解。
这道题是:POJ1201 可以练练手
代码如下
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , dist[50005] , head[50005] , tp , maxt ;
struct Path{
int pre , to , len ;
}p[200005] ;
void In( int t1 , int t2 , int t3 ){
p[++tp].pre = head[t1] ;
p[head[t1] = tp].to = t2 ;
p[tp].len = t3 ;
}
int que[5000005] , ba , fr ;
bool inque[50005] ;
void SPFA(){
memset( dist + 1, -0x3f , ( maxt + 1 ) * sizeof( int ) ) ;
ba = 2500000 ; fr = 2500001 ;
que[++ba] = 0 ; inque[0] = true ;
while( fr <= ba ){
int u = que[fr++] ; inque[u] = false ;
for( int i = head[u] ; i ; i = p[i].pre ){
int v = p[i].to , len = p[i].len ;
if( dist[v] < dist[u] + len ){
dist[v] = dist[u] + len ;
if( !inque[v] ){
if( dist[v] >= dist[que[fr]] )
que[--fr] = v ;
else que[++ba] = v ;
inque[v] = true ;
}
}
}
}
printf( "%d" , dist[maxt] ) ;
}
int main(){
int t1 , t2 , t3 ;
scanf( "%d" , &N ) ;
for( int i = 1 ; i <= N ; i ++ ){
scanf( "%d%d%d" , &t1 , &t2 , &t3 ) ;
In( t1 - 1 , t2 , t3 ) ; maxt = max( maxt , t2 ) ;
}
for( int i = 0 ; i < maxt ; i ++ ){
In( i , i + 1 , 0 ) ;
In( i+1 , i , -1 ) ;
}
SPFA() ;
return 0 ;
}
注:me的Bellman-Ford(该算法队列优化俗称SPFA)均使用双端队列优化,这样写在很多情况下会快不少,有些情况下会变慢(只遇到过一次)…有兴趣的同学也可以试试stack版的Bellman-Ford,这个有时候也会快,但是慢的时候占绝大多数
注2:其实不是很喜欢SPFA这个称呼,明明就是队列优化= =
一些其他的题
POJ2983:
题意
给你一堆限制条件,这些限制条件分成两类,确定的(用P表示)和不确定的(用V)表示
[P a b c]表示:a-b=c
[V a b]表示:a-b≥1
若这些条件没有冲突,输出Reliable,不然输出Unreliable
这破题怎么做?
其实难点在于取等,差分约束只能解决大于等于符号,而无法解决等于符号。于是需要将等式用不等式表示如下
这两个不等式如果都要成立,相当于只能取等
建边这样建:
不等式:a到b,长度为1
等式:a到b,长度为val。b到a,长度为-val
直接跑最短路判断有没有负环就好了
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , M , head[1005] , dist[1005] , tp ;
int que[5000005] , fr , ba , cnt[1005] ;
bool inque[1005] ;
struct Path{
int pre , to , len ;
}p[200005] ;
void clear(){
tp = 0 ;
memset( head , 0 , sizeof( head ) ) ;
memset( cnt , 0 , sizeof( cnt ) ) ;
memset( dist , 0x3f , sizeof( dist ) ) ;
}
void In( int t1 , int t2 , int t3 ){
p[++tp].pre = head[t1] ;
p[ head[t1] = tp ].to = t2 ;
p[tp].len = t3 ;
}
bool SPFA(){
fr = 2500001 ; ba = 2500000 ;
for( int i = 1 ; i <= N ; i ++ )
que[++ba] = i , inque[i] = true ;
while( fr <= ba ){
int u = que[fr++] ; inque[u] = false ;
for( int i = head[u] ; i ; i = p[i].pre ){
int v = p[i].to , len = p[i].len ;
if( dist[v] > dist[u] + len ){
dist[v] = dist[u] + len ;
if( !inque[v] ){
if( dist[v] < dist[que[fr]] ) que[--fr] = v ;
else que[++ba] = v ;
}
inque[v] = true ; ++ cnt[v] ;
if( cnt[v] > N + 2 ) return false ;
}
}
}
return true ;
}
int main(){
while( scanf( "%d%d" , &N , &M ) != EOF ){
clear() ;
char opt ;
int a , b , x ;
for( int i = 1 ; i <= M ; i ++ ){
opt = getchar() ;
while( opt != 'P' && opt != 'V' ) opt = getchar() ;
switch ( opt ){
case 'P' :
scanf( "%d%d%d" , &a , &b , &x ) ;
In( a , b , x ) ;
In( b , a , -x ) ;
break ;
case 'V' :
scanf( "%d%d" , &a , &b ) ;
In( b , a , -1 ) ;
}
}
puts( SPFA() ? "Reliable" : "Unreliable" ) ;
}
}
POJ3159:
题意
一群小屁孩分糖果,他们的编号为1到N。要求编号为Bi小孩子分得的糖果不能超过编号为Ai的小孩子Ci个,不然他们就会不满意(= =给糖还不满意,不如给me这个穷得吃土的可怜孩子qwq)
询问在所有小孩都满意的情况下,N号小孩子最多比1号小孩子多几个糖果
Emmmmm,注意有多组数据。。。
题解
这题其实和普通的差分区别不大,只是这个题要跑最短路
如果不能理解的话,我们把这个题的题面转换一下:数轴上有一堆点,已知Bi点所在位置不大于Ai所在位置+Ci,询问N号点位置最远是多少。相当于是求出符合条件N的最大值。
条件写成式子:
按照从A向B,长度为C的方式建边。初始化dist数组为无限大然后跑最短路,那么这条最短路一定符合所有限制条件(符合所有上述式子)
之后就是常规建图跑了
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , M , head[30005] , tp ;
struct Path{
int to , pre , len ;
}p[150005];
void In( int t1 , int t2 , int t3 ){
p[++tp].pre = head[t1] ;
p[ head[t1] = tp ].to = t2 ;
p[tp].len = t3 ;
}
int que[25000005] , dist[30005] , fr , ba ;
bool inque[30005] ;
void SPFA(){
fr = 12500001 ; ba = 12500000 ;
memset( dist , 0x3f , ( N + 1 ) * sizeof( int ) ) ;
que[++ba] = 1 ; inque[1] = true ; dist[1] = 0 ;
while( fr <= ba ){
int u = que[fr++] ; inque[u] = false ;
for( int i = head[u] ; i ; i = p[i].pre ){
int v = p[i].to , len = p[i].len ;
if( dist[v] > dist[u] + len ) {
dist[v] = dist[u] + len ;
if( !inque[v] ){
if( dist[v] <= dist[que[fr]] + 10 ) que[--fr] = v ;
else que[++ba] = v ;
}
inque[v] = true ;
}
}
}
}
int main(){
int a , b , c ;
while( scanf( "%d%d" , &N , &M ) != EOF ){
memset( head , 0 , sizeof( head ) ) ;
tp = 0 ;
for( register int i = 1 ; i <= M ; i ++ ){
scanf( "%d%d%d" , &a , &b , &c ) ;
In( a , b , c ) ;
}
SPFA() ;
printf( "%d\n" , dist[N] ) ;
}
}
POJ1364
题意
已知一个序列a[1], a[2], ……, a[n],给出一些对子串的限制,每个限制形如
[s n gt/lt k](gt表示大于,lt表示小于),这表示:
询问是否有满足所有限制条件的序列
题解
这题,其实不算很难想的
题目上的限制都是针对一段子串的和,明显是不能直接把式子写成像这样的:
因为这样是不符合A-B≤val的形式,未知数超过了两个,无法建边
于是考虑把多个值相加转换成前缀和相减
定义S数组:S[i]为a[1]到a[i]的和
那么上面的式子可以表示为:
剩下的步骤和普通题都差不多了,几乎就是上面讲到的POJ1201照搬就可以
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , M , tp , head[105] , dist[105] , cnt[105] ;
struct Path{
int pre , to , len ;
}p[205] ;
void clear(){
tp = 0 ;
memset( head , 0 , sizeof( head ) ) ;
memset( dist , 0x3f , sizeof( dist ) ) ;
memset( cnt , 0 , sizeof( cnt ) ) ;
}
void In( int t1 , int t2 , int t3 ){
p[++tp].pre = head[t1] ;
p[ head[t1] = tp ].to = t2 ;
p[tp].len = t3 ;
}
bool inque[105] ;
int que[50005] , fr , ba ;
bool SPFA(){
fr = 25001 ; ba = 25000 ;
for( int i = 0 ; i <= N ; i ++ )//注意是前缀和,从S[0]开始计算,0也要push进去
que[++ba] = i , inque[i] = true ;
while( fr <= ba ){
int u = que[fr++] ; inque[u] = false ;
for( int i = head[u] ; i ; i = p[i].pre ){
int v = p[i].to , len = p[i].len ;
if( dist[v] > dist[u] + len ){
dist[v] = dist[u] + len ;
if( !inque[v] ){
if( dist[v] <= dist[que[fr]] ) que[--fr] = v ;
else que[++ba] = v ;
}
inque[v] = true ; cnt[v] ++ ;
if( cnt[v] > N ) return false ;
}
}
}
return true ;
}
int main(){
int a , b , Val ;
char ss[5] ;
while( scanf( "%d" , &N ) && N && scanf( "%d" , &M) ){
clear() ;
for( int i = 1 ; i <= M ; i ++ ){
scanf( "%d%d%s%d" , &a , &b , ss , &Val ) ;
switch ( ss[0] ){
case 'g' : In( a+b , a-1 , -Val-1 ) ;break ;
case 'l' : In( a-1 , a+b , Val-1 ) ;break ;
}
}
puts( SPFA() ? "lamentable kingdom" : "successful conspiracy" ) ;
}
return 0 ;
}
POJ1275
说在前面
这个题可以说是差分题当中相当难的,需要转换未知量,但是写起来并不难,是道很不错的题,推荐写一写
题意:
有一家全天营业的超市,这家超市在每天的不同时段需要不同数目的出纳员(例如,午夜只需一小批,而下午则需要很多)来为顾客提供优质服务,超市经理希望雇佣最少数目的纳员。
超市经理会提供一天里每一小时需要出纳员的最少数量:R(0),R(1),…,R(23)。R(0)表示从午夜到凌晨1:00所需要出纳员的最少数目(可以比这个数目多),R(1)表示凌晨1:00到2:00之间需要的,以此类推。
现在有N人申请这项工作,每个申请者i都会从一个特定的时刻开始连续工作恰好8小时。定义ti(0<=ti<=23)为上面提到的开始时刻,也就是说,如果第i个申请者被录用,他(或她)将从ti时刻开始连续工作8小时。
计算为满足上述限制需要雇佣的最少出纳员数目。
题解
说实话,这个题,不是很想写题解,写起来能写一小时···
要注意的地方实在很多。
就在这里引用一下别人的题解吧
hr_whisper的POJ1275题解
大牛解释如下:
为避免负数,时间计数1~24。令:
R[i] i时间需要的人数 (1<=i<=24)
T[i] i时间应聘的人数 (1<=i<=24)
x[i] i时间录用的人数 (0<=i<=24),其中令x[0]=0
再设s[i]=x[0]+x[1]+……+x[i] (0<=i<=24),
由题意,可得如下方程组:
(1) s[i]-s[i-8]>=R[i] (8<=i<=24)
(2) s[i]-s[16+i]>=R[i]-s[24] (1<=i<=7)
(3) s[i]-s[i-1]>=0 (1<=i<=24)
(4) s[i-1]-s[i]>=-T[i] (1<=i<=24)这个差分约束有个特殊的地方,(2)的右边有未知数s[24]。
这时可以通过枚举s[24]=ans来判断是否有可行解。
即(2)变形为(2’) s[i]-s[16+i]>=R[i]-ans (1<=i<=7)
再通过SPFA求解(1)(2’)(3)(4)。不过最后有可能出现这种情况:
(1)(2’)(3)(4)虽然有解,但求出的s[24]小于代入(2’)里的ans!
这时,显然得到的s[]不满足原来的(2)了(请仔细比较(2)与(2’))。
不过虽然得到的解不满足原方程组,但这并不代表(1)(2)(3)(4)在s[24]=ans时没有可行解!
此外,值得注意的是,当得到的s[24]>ans时,虽然s[24]不一定是最优解,但把ans置成s[24]后,确实是可行解。所以,简单把(2)置换成(2’)是有问题的!
为了等价原命题,必须再加上条件:s[24]>=ans
这就是所谓加出来的那条边(5) s[24]-s[0]>=ans
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int Tot , N , R[30] , T[30] , head[30] , tp , ans , dist[30] ;
struct Path{
int pre , to , len ;
}p[200] ;
void In( int t1 , int t2 , int t3 ){
p[++tp].pre = head[t1] ;
p[ head[t1] = tp ].to = t2 ;
p[tp].len = t3 ;
}
void addE( int x ){
tp = 0 ;
memset( head , 0 , sizeof( head ) ) ;
for( int i = 1 ; i <= 24 ; i ++ ){
In( i - 1 , i , 0 ) ;
In( i , i - 1 , -T[i] ) ;
}
for( int i = 8 ; i <= 24 ; i ++ )
In( i - 8 , i , R[i] ) ;
for( int i = 1 ; i <= 7 ; i ++ )
In( 16 + i , i , R[i] - x ) ;
In( 0 , 24 , x ) ;
}
bool inque[25] ;
int que[50005] , fr , ba , cnt[25] ;
bool SPFA( int x ){
memset( dist , ~0x3f , sizeof( dist ) ) ;
memset( cnt , 0 , sizeof( cnt ) ) ;
memset( inque , 0 , sizeof( inque ) ) ;
fr = 25001 ; ba = 25000 ;
que[++ba] = 0 ; dist[0] = 0 ; inque[0] = true ;
while( fr <= ba ){
int u = que[fr++] ; inque[u] = false ;
for( int i = head[u] ; i ; i = p[i].pre ){
int v = p[i].to , len = p[i].len ;
if( dist[v] < dist[u] + len ){
dist[v] = dist[u] + len ;
if( !inque[v] ){
if( dist[v] > dist[que[fr]] ) que[--fr] = v ;
else que[++ba] = v ;
inque[v] = true ; ++ cnt[v] ;
if( cnt[v] > 25 ) return false ;
}
}
}
}
if( dist[24] != x ) return false ;
return true ;
}
void solve(){
int lf = 0 , rg = N ; ans = -1 ;
while( lf <= rg ){
int mid = ( lf + rg ) >> 1 ;
addE( mid ) ;
if( SPFA( mid ) ){
rg = mid - 1 ;
ans = mid ;
} else lf = mid + 1 ;
}
}
int main(){
scanf( "%d" , &Tot ) ;
while( Tot-- ){
for( int i = 1 ; i <= 24 ; i ++ )
scanf( "%d" , &R[i] ) ;
scanf( "%d" , &N ) ;
for( int i = 1 , j ; i <= N ; i ++ )
scanf( "%d" , &j ) , T[j+1] ++ ;
solve() ;
printf( ans == -1 ? "No Solution\n" : "%d\n" , ans ) ;
}
}