【差分约束(spfa版)】总结

[size=medium]KIDx 的解题报告[/size]
[img]http://dl.iteye.com/upload/attachment/564239/e38e667f-31a8-31d7-8b87-798e36f8f74c.png[/img]
[b][size=medium]
[color=blue]先总结下:[/color]

[color=green]第一:[/color]
感觉难点在于建图
[color=green]第二:[/color]
[color=brown]①:对于差分不等式,a - b <= c ,建一条 b 到 a 的权值为 c 的边,求的是最短路,得到的是最大值
②:对于不等式 a - b >= c ,建一条 b 到 a 的权值为 c 的边,求的是最长路,得到的是最小值
③:存在负环的话是无解
④:求不出最短路(dist[ ]没有得到更新)的话是任意解[/color]
[color=green]第三:[/color]
一种建图方法:
[color=brown]设x[i]是第i位置(或时刻)的值(跟所求值的属性一样),那么把x[i]看成数列,前n项和为s[n],则x[i] = s[i] - s[i-1];[/color]
那么这样就可以最起码建立起类似这样的一个关系:0 <= s[i] - s[i-1] <= 1;
其他关系就要去题目探索了

[color=blue]回到上面那些题目:[/color][color=green]

第一题:【POJ 1201/ZOJ 1508/HDU 1384 Intervals】
[/color][url]http://poj.org/problem?id=1201[/url]
题意:求符合题意的最小集合的元素个数
设x[i]是[color=red]{i}[/color]这个集合跟所求未知集合的交集元素个数,[color=red]明显最大只能是1[/color]
再设s[i] = x[0] + x[1] + …… + x[i]
明显的,s[i]表示集合[color=red]{0,1,2,3,……,i}[/color]与所求未知集合的交集元素个数
那么就有x[i] = s[i] - s[i-1]
∵0 <= x[i] <= 1
∴0 <= s[i] - s[i-1] <= 1
由于题目求最小值,所以是最长路,用的是[color=red]a - b >= c[/color]这种形式
即有:[color=red]①s[i] - s[i-1] >= 0; ②s[i-1] - s[i] >= -1;[/color]
按照题目输入a, b, c:
表示[color=red]{a,a+1,a+2,……,b}[/color](设这个集合是Q)与所求未知集合的交集元素个数[color=red]至少为c[/color]
而s[a-1]表示[color=red]{1,2,3,……,a-1}[/color]与所求未知集合的交集元素个数
s[b]表示[color=red]{1,2,3,……,a-1,a,a+1,a+2,……,b}[/color]与所求未知集合的交集元素个数
∴Q = s[b] - s[a-1];
即可建立关系: ③[color=red]s[b] - s[a-1] >= c;[/color]
但是还有一个问题a >= 0,那么a-1有可能不合法
解决方法:所有元素+1就可以了
实现:把③变成 [color=red]s[b+1] - s[a] >= c;[/color]

[color=green]第二题:【POJ 1275/ZOJ 1420/HDU 1529 Cashier Employment】[/color]
[url]http://poj.org/problem?id=1275[/url]
[color=blue]文章最后有附上这题的代码。
这题是这里面最难的一题,建图难,而且难以找出所有约束条件[/color]
[color=orange]先说明一下,这里我把编号设定为1-24,而不是题目的0-23,并且设0为虚点[/color]
设x[i]是[color=red]实际雇用[/color]的[color=red]i时刻开始工作的[/color]员工数
R[i]是题目需要的[color=red]i时刻正在工作的[/color][color=blue]最少员工数[/color]
[color=blue]注意了,[/color][color=red]i时刻开始工作[/color]跟[color=red]i时刻正在工作[/color][color=blue]是完全不同的[/color]
设s[i] = x[1] + x[2] + …… + x[i]
则s[i]表示1-i这段时间开始工作的员工数
再设num[i]表示[color=red]i时刻开始工作[/color]的[color=red]最多可以雇用的[/color]员工数
∴有0 <= x[i] <= num[i]
即 0 <= s[i] - s[i-1] <= num[i]
由于是求最小值,所以用最长路
则有:[color=red]①s[i] - s[i-1] >= 0;②s[i-1] - s[i] >= -num[i];
[/color]([color=blue]1 <= i <= 24,虽然0是虚点,但是s[1] - s[0]也是必要的!因为x[1]也是有范围的![/color])
由于员工可以持续工作8个小时(R[i]是i时刻正在工作的最少人数)
∴x[i-7] + x[i-6] + …… + x[i] >= R[i]【i-7开始工作的人在i时刻也在工作,其他同理】
即:[color=red]③s[i] - s[i-8] >= R[i][/color] ([color=blue]8 <= i <= 24[/color])
但是有个特殊情况,就是从夜晚到凌晨的一段8小时工作时间
(x[i+17] + …… + x[24]) + (x[1] + x[2] + …… + x[i]) >= R[i];
则:s[24] - s[i+16] + s[i] >= R[i];
整理一下:[color=red]④s[i] - s[i+16] >= R[i] - [/color][color=blue]s[24][/color];
([color=blue]1 <= i < 8,注意i=0是没有意义的,因为R[0]没有意义[/color])
由于s[24]就是全天实际雇用的人数,而一共有n个员工可以雇用
所以设ans = s[24]
则:[color=red]⑤s[i] - s[i+16] >= R[i] - [/color][color=blue]ans[/color];[color=blue]( 1 <= i < 8 )[/color]
所以就可以从小到大暴力枚举ans【或二分枚举】,通过spfa检验是否有解即可【存在负环无解】
但是还有一个问题,起点在哪里……
这时候虚点0就起作用了,我称它为[color=red]超级起点[/color][img]http://dl.iteye.com/upload/attachment/564384/e0b579af-6ce9-3268-b6f7-834d133d19fe.gif[/img]
于是建图后直接判断spfa(0)是否有解就可以了
[color=blue]PS:还有另外一个条件必须用到……:[/color][color=red]⑥s[24] - s[0] >= ans][/color]
不用这个条件二分枚举ans可以AC,但这只是数据问题,[color=red]暴力从小到大枚举木有这条件就会错,所以说这个条件最关键而又难找……要仔细找特殊点和虚点的约束关系[/color]

[color=green]第三题:【POJ 1364/ZOJ 1260/HDU 1531 King】[/color]
[url]http://poj.org/problem?id=1364[/url]
设s[i] = a[1] + a[2] + …… + a[i];
a[Si] + a[Si+1] + ... + a[Si+ni] = s[Si+ni] - s[Si-1];
所以由题意有:
①s[Si+ni] - s[Si-1] > ki
或②s[Si+ni] - s[Si-1] < ki
由于只是检验是否有解,所以最短路或最长路都可用,以下是最短路要建立的关系:
把①化为:[color=red]s[si-1] - s[si+ni] <= - ki - 1[/color]
把②化为:[color=red]s[si+ni] - s[si-1] <= ki - 1[/color]

[color=green]第四题:【ZOJ 1455/HDU 1534 Schedule Problem】[/color]
[url]http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1455[/url]
设第i项工作持续时间为v[i],开始时间s[i],那么结束时间就是s[i]+v[i]
[color=red]SAS: s[a] >= s[b] ---> s[a] - s[b] >= 0
FAS: s[a] + v[a] >= s[b] ---> s[a] - s[b] >= -v[a]
SAF: s[a] >= s[b] + v[b] ---> s[a] - s[b] >= v[b]
FAF: s[a] + v[a] >= s[b] + v[b] ---> s[a] - s[b] >= v[b] - v[a][/color]
直接最长路建图就可以了

[color=green]第五题:【HDU 3440 House Man】[/color]
[url]http://acm.hdu.edu.cn/showproblem.php?pid=3440[/url]
[color=blue]此题难度仅次于第二题,需要深刻理解差分约束[/color]
题意:按照序号从左往右放置房子,求最后从低到高跳到所有房子的【[color=red]最大总水平路程[/color]】
设s(b) - s(a) <= K(一个常数)表示【设a,b为序号】:
b到a的距离 <= K,但是必须定一个规则,a在左边还是b在左边?
这里设a,b是x轴上的点,再设b > a
所以这样的情况下规则就是:【[color=red]s(序号大的) - s(序号小的)[/color]】才表示:【[color=red]b到a之间的距离[/color]】这个意义
当然可以设另一种规则---【序号小-序号大】表示距离---,总之要一致!
按照第一种规则有以下关系:
①位置相邻:
[color=red]s(i) - s(i-1) >= 1 ---> s(i-1) - s(i) <= -1[/color]
②高度相邻(排序后):【num表示a[i]这间房子的序号】
[color=red]s([/color]max[color=red](a[i].num,a[i-1].num))-s([/color]min[color=red](a[i].num,a[i-1].num)) <= D[/color]
建图后只要这样:
[color=violet]spfa (min (a[1].num, a[n].num));
printf ("%d\n", dist[max (a[n].num, a[1].num)]);[/color]
答案就出来了
另外推荐这种方法!比较简单:[url]http://hi.baidu.com/tju_ant/blog/item/f22fe6d92809033833fa1c08.html[/url]

[color=green]第六题:【HDU 3592 World Exhibition】[/color]
[url]http://acm.hdu.edu.cn/showproblem.php?pid=3592[/url]
按照题目明显条件建立关系式立刻水过
但是我觉得它数据很水……
题目说:Assume that there are N (2 <= N <= 1,000) people numbered 1..N who are [color=red]standing in the same order as they are numbered[/color]
也就是说人是按序号排的啊
那应该还有一个约束条件才对吧:s(i) - s(i-1) >= 0;
就像这组数据:
1
3 2 1
1 2 1
1 3 2
2 3 3
要按序号排队不可能满足,应该输出-1
总之要不要这个约束条件都能AC……[color=orange]我特么的彻底无语了[/color][img]http://dl.iteye.com/upload/attachment/564384/e0b579af-6ce9-3268-b6f7-834d133d19fe.gif[/img]


[color=green]第七题:【HDU 3666 THE MATRIX PROBLEM】[/color]
[url]http://acm.hdu.edu.cn/showproblem.php?pid=3666[/url]
[color=blue]这题spfa用队列卡数据容易超时,用栈吧……[/color]
由题意得对于矩阵每个元素【设为w,[color=red]处于矩阵第i行,第j列[/color]】
L <= w*a[i]/b[j] <= U
两边取对数有:
lg(L) <= lg(w)+lg(a[i])-lg(b[j]) <= lg(U)
按照最长路整理得【也可以用最短路】:
[color=red]①lg(a[i])-lg(b[j]) >= lg(L)-lg(w);②lg(b[j])-lg(a[i]) >= lg(w)-lg(U)[/color]
把a和b数组合并成一个数组s得【a有n个元素,b有m个元素,这里把b接在a数组后面】:
[color=red]①s(i) - s(j+n) >= lg(L) - lg(w)
②s(j+n) - s(i) >= lg(w) - lg(U)[/color]
最后加个超级起点即可
我把0作为超级起点:
[color=violet]for (i = 1; i <= n + m; i++)
addedge (0, i, 0);
spfa (0); [/color]
[/size][/b]

[b][size=medium]最后弱弱的献上第二题代码:[/size][/b]

#include <iostream>
#include <queue>
using namespace std;
#define inf 0x7fffffff
#define M 25

struct edge{
int v, w, next;
}e[5*M];
bool inq[M];
int dist[M], pre[M], n = 24, cnt, ind[M];

void init ()
{
memset (pre, -1, sizeof(pre)); cnt = 0;
}
void addedge (int u, int v, int w)
{
e[cnt].v = v; e[cnt].w = w; e[cnt].next = pre[u]; pre[u] = cnt++;
}
bool spfa (int u)
{
int i, v, w;
for (i = 0; i <= n; i++)
{
dist[i] = -inf;
inq[i] = false;
ind[i] = 0;
}
queue<int> q;
q.push (u);
inq[u] = true;
dist[u] = 0;
while (!q.empty())
{
u = q.front();
if (++ind[u] > n)
return false;
q.pop();
inq[u] = false;
for (i = pre[u]; i != -1; i = e[i].next)
{
v = e[i].v;
w = e[i].w;
if (dist[u] + w > dist[v])
{
dist[v] = dist[u] + w;
if (!inq[v])
{
q.push (v);
inq[v] = true;
}
}
}
}
return true;
}

int main()
{
int t, R[M], num[M], i, pos, m, l, r, ans;
scanf ("%d", &t);
while (t--)
{
for (i = 1; i <= n; i++)
scanf ("%d", R+i);
scanf ("%d", &m);
memset (num, 0, sizeof(num));
for (i = 0; i < m; i++)
{
scanf ("%d", &pos);
num[pos+1]++;
}
l = 0, r = m;
bool flag = true;
while (l < r) //二分枚举ans
{
init();
ans = (l+r) / 2;
for (i = 1; i <= n; i++)
{
addedge (i-1, i, 0); //s[i] - s[i-1] >= 0
addedge (i, i-1, -num[i]); //s[i-1] - s[i] >= -num[i]
}
for (i = 8; i <= n; i++)
addedge (i-8, i, R[i]); //s[i] - s[i-8] >= R[i]
for (i = 1; i < 8; i++)
addedge (i+16, i, R[i]-ans); //s[i] - s[i+16] >= R[i] - ans
addedge (0, 24, ans); //s[24] - s[0] >= ans
if (spfa (0))
r = ans, flag = false;
else l = ans + 1;
}
if (flag)
puts ("No Solution");
else printf ("%d\n", r);
}
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值