您的位置:首页 > 其它

差分约束系统入门

2016-09-08 19:36 183 查看
这篇主要给出几道题目,不了解差分约束系统的可以看一下我转载的这篇:差分约束系统详解

简介

简单说一下差分约束系统,顾名思义,差分约束就是给出一些条件,要求在满足这些条件的前提下,系统的可行解。当然约束的形式大多数是不等式,然后再满足给定不等式的前提下,系统是否存在可行解。但是如何在满足不等式的条件下去寻找可行解(或者判断是否有可行解)呢?这时最短路挺身而出了!为何最短路可以解决这类题呢?

来看一下不等式的形式:

a<=b+1

再来看一下最短路的松弛条件:

if(dis[a]>dis[b]+1)dis[a]=dis[b]+1

是不是存在某种关系呢?

因为不等式要求a<=b+1,但是如果a>b+1,那这就不满足条件了,所以我们让a=b+1,这不就可以让不等式成立了!而这个思维过程不就是最短路的松弛过程嘛!

if(a>b+1)a=b+1 <=>if(dis[a]>dis[b]+1)dis[a]=dis[b]+1

所以差分约束系统可以用最短路求解!把不等式关系用边表示出来,然后求最短路即可。

这时求出来的解是符合条件的最大值!为什么是最大值呢?看一下松弛过程if(a>b+1)a=b+1 在不满足不等式的时候,我们让a=b+1,a在所有符合条件的范围内是最大的!同理,所以其它值在符合不等式的条件下都是最大的!

我们再来看一下建边的过程:因为a<=b+1,也就是说b再最多经过1长度就可以到达a了,所以我们在b->a见一条有向边,最大长度是1(这里也可以体现出最大值的特性)。

上面大体介绍了用最短路求解a<=b+1形式的不等式条件,并且解释了为什么得到的可行解是最大的。

如果不等式的形式是a>=b+1,那么相应的求的就是最长路。为什么?请从最长路的松弛条件去考虑,这个过程跟我上面的分析过程是类似的。

我转载的那篇文章总结了一些结论:

.>=,求最小值,做最长路;

<=,求最大值,做最短路。

边都是从后往前~~~

<=构图。

有负环说明无解。

求不出最短路(为Inf)为任意解。

.>=构图时类似。

这个结论看一下就好,理解了根本不需要去记忆,从最短路的角度去理解更好!

最短路也有很多算法,dijkstra,bellman,spfa等,如果没有负边,那么都可以,而且dij的nlogn的算法更快,一般选dij。但如果有负边,就要选bellman和spfa了,一般我用spfa,Bellman时间复杂度O(nm),太慢!而spfa虽然最坏情况下也是O(nm),但是通常比Bellman快很多(其实spfa是Bellman的优化,二者的思想是一样的)!

现在开始做题目,通过题目来加深理解!

题目:poj 2983

题意:

N 个 defense stations,M条消息,消息有精确和模糊之分,若前边为P。则为精确消息,有两个defense stations和他们之间的距离,若为P A B X 则是精确消息,并且A在B北边X光年远,V A B则是模糊消息,A在B北边,不知道距离,但至少是1光年远。

分析:

首先寻找不等式:

P A B X -> A=B+X -> A>=B+X&&A<=B+X ->B<=A-X&&A<=B+X

V A B -> A>=B+1 -> B<=A-1

找出不等式以后,建边,spfa求最短路,然而WA了~~。然后看讨论,发现图可能不连通,对啊!我咋没想到呢QAQ。加个超级源点就行。

代码:

const int INF = 1e9 + 9;
const int mod = 1000007;
const int N = 100000 + 9;
struct Edge {
int v, w, next;
} edge[3 * N];
int head
, d
, vis
, n,cnt,num
;
void addedge (int u, int  v, int w) {
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
int spfa(int s,int t) {
queue<int>q;
for (int i = s; i <= t; i++) d[i] = INF,q.push(i); //相当于加了超源点
memset(num,0,sizeof(num));
d[s] = 0;
while (!q.empty() ) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; ~i; i = edge[i].next) {
if (d[edge[i].v] > d[u] + edge[i].w) {
d[edge[i].v] = d[u] + edge[i].w;
if (!vis[edge[i].v]) {
q.push (edge[i].v);
vis[edge[i].v] = 1;
if(++num[edge[i].v]>n)return 0;
}

}
}
}
return 1;
}
char op[10];
int main() {
// freopen ("f.txt", "r", stdin);
int u, v, w,m;
while (~scanf ("%d%d", &n,&m) ) {
memset (head, -1, sizeof (head) );
memset (vis, 0, sizeof (vis) );
memset (edge, 0, sizeof (edge) );
cnt = 0;

for (int i = 0; i < m; i++) {
scanf ("%s%d%d",op, &u, &v);
if(op[0]=='P'){
scanf("%d",&w);
addedge(v,u,w);
addedge(u,v,-w);
}
else addedge(u,v,-1);
}
if(spfa(1,n))puts("Reliable");
else puts("Unreliable");
}
return 0;
}


题目:poj1201 &&poj1716

题意:

给出n个闭区间[ai,bi],每个区间对应一个ci,表示集合Z在区间[ai,bi]内ci个相同元素,问集合Z至少有几个元素。

分析:

这题是1716那题的进阶版,当然那题贪心一下就搞定了~~。

不难想到用sum[a]表示从0到a一共有多少个集合中的元素。那么不等式就显然了:

sum[bi]-sum[ai-1]>=ci,因为ai最少是0,ai-1可能是负,所以我们集体右移一个单位:sum[bi+1]-sum[ai]>=ci 。这一个等式就可以了吗?那得看我们的定义!我们定义的是sum[a]表示从0到a一共有多少个集合中的元素!这就隐含了一些等式了:

sum[a+1]-sum[a]>=0&&sum[a+1]-sum[a]<=1。这就差不多了,我们整理一下等式,因为题目要求的是最小值,所以我们第一反应是整理成最长路的形式:

sum[bi+1]-sum[ai]>=ci

sum[a+1]-sum[a]>=0

sum[a]-sum[a+1]>=-1

好了,然后建边,spfa最长路,顺利AC!

然而这题最短路也是可以的!为什么呢?自己思考吧(嘿嘿)。提示:因为我们的定义!sum[0]=0!有这个等式的存在,那么就使得最短路和最长路的解是一样的!

代码:

const int INF = 1e9 + 9;
const int mod=1000007;
const int N = 50000 + 9;
struct Edge
{
int v,w,next;
}edge[3*N];
int head
,d
,vis
,n,Max,Min,cnt;
void addedge(int u,int  v,int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void spfa()
{
for(int i=Min;i<=Max;i++)d[i]=-INF;
d[Min]=0;
queue<int>q;
q.push(Min);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(int i=head[u];~i;i=edge[i].next)
{
if(d[edge[i].v]<d[u]+edge[i].w){
d[edge[i].v]=d[u]+edge[i].w;
if(!vis[edge[i].v]){
q.push(edge[i].v);
vis[edge[i].v]=1;
}
}
}
}
}
int main() {
// freopen ("f.txt", "r", stdin);
int u,v,w;
while(~scanf("%d",&n)){
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(edge,0,sizeof(edge));
cnt=0;
Min=INF,Max=-INF;
for(int i=0;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
addedge(u,v+1,w); //1716w换成2即可
if(Min>u)Min=u;
if(Max<v+1)Max=v+1;
}
for(int i=Min;i<Max;i++){
addedge(i,i+1,0);
addedge(i+1,i,-1);
}
spfa();
printf("%d\n",d[Max]);
}
return 0;
}


题目:poj 3159

题意:

给n个人派糖果,给出m组数据,每组数据包含A,B,c 三个数,意思是A的糖果数比B少的个数不多于c,即B的糖果数 - A的糖果数<= c 。最后求n 比 1 最多多多少糖果。

分析:

还是和上面两题一样,先找不等式:

B-A<=c(比较显然)

然后这题c非负,而且边的数目是 150 000 ,目测spfa超时,果断dij,顺利1A~~

然后看了discuss,发现这题栈优化的spfa也可以过.

代码:

typedef pair<int, int>pii;
const int INF = 1e9 + 9;
const int mod = 1000007;
const int N = 150000 + 9;
struct Edge {
int v, w, next;
} edge
;
int head
, d
, vis
, n, cnt, num
;
void addedge (int u, int  v, int w) {
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
void dijkstra() {
priority_queue<pii, vector<pii>, greater<pii> >q;
for (int i = 1; i <= n; i++) d[i] = INF;
d[1] = 0;
q.push (pii (0, 1) );
while (!q.empty() ) {
pii t = q.top();
q.pop();
int u = t.second;
if (vis[u]) continue;
vis[u] = 1;
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].v;
int w = edge[i].w;
if (d[v] > d[u] + w) {
d[v] = d[u] + w;
q.push (pii (d[v], v) );
}
}
}
}

int main() {
// freopen ("f.txt", "r", stdin);
int a, b, w, m;
while (~scanf ("%d%d", &n, &m) ) {
memset (head, -1, sizeof (head) );
memset (vis, 0, sizeof (vis) );
memset (edge, 0, sizeof (edge) );
cnt = 0;

for (int i = 0; i < m; i++) {
scanf ("%d%d%d", &a, &b, &w);
addedge (a, b, w);
}
dijkstra();
printf ("%d\n", d
);
}
return 0;
}


题目:poj 1364

题意:

已知一个序列a[1], a[2], ……, a
,给出它的若干子序列以及对该子序列的 约束条件,例如a[si], a[si+1], a[si+2], ……, a[si+ni],且a[si]+a[si+1]+a[si+2]+……+a[si+ni] < or > ki。求是否存在满足以上m个要求的数列。是则输出“lamentable kingdom”,否则输出“successful conspiracy”。

分析:

这题也是表示成前缀和的形式

gt sum[a+b]-sum[a-1]>ki

lt sum[a+b]-sum[a-1]< ki,

转换成不等式的形式,并且用最短路求解:

sum[a-1]<=sum[a+b]-k-1

sum[a+b]<=sum[a-1]+ki+1

然后建边,spfa最短路,注意图可能不连通

代码:

const int INF = 1e9 + 9;
const int mod = 1000007;
const int N = 100000 + 9;
struct Edge {
int v, w, next;
} edge
;
int head
, d
, vis
, n, cnt, num
;
void addedge (int u, int  v, int w) {
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
int spfa (int s, int t) {
queue<int>q;
for (int i = s; i <= t; i++) d[i] = INF, q.push (i);//相当于超源点
memset (num, 0, sizeof (num) );
d[s] = 0;

while (!q.empty() ) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; ~i; i = edge[i].next) {
if (d[edge[i].v] > d[u] + edge[i].w) {
d[edge[i].v] = d[u] + edge[i].w;
if (!vis[edge[i].v]) {
q.push (edge[i].v);
vis[edge[i].v] = 1;
if (++num[edge[i].v] > n) return 0;
}

}
}
}
return 1;
}
char op[10];
int main() {
//freopen ("f.txt", "r", stdin);
int a,b, w, m;
while (~scanf ("%d%d", &n, &m)&&n ) {
memset (head, -1, sizeof (head) );
memset (vis, 0, sizeof (vis) );
memset (edge, 0, sizeof (edge) );
cnt = 0;

for (int i = 0; i < m; i++) {
scanf ("%d%d%s%d", &a, &b,op,&w);
if (op[0] == 'g') {
addedge(a+b,a-1,-w-1);
} else addedge (a-1,a+b, w-1);
}
if (spfa (0, n) ) puts ("lamentable kingdom");
else puts ("successful conspiracy");
}
return 0;
}


上面介绍了几道比较简单的差分约束系统的题,说它简单,是因为模型比较简单,不等式很容易想到。有的题目模型是比较复杂的,这就需要好好想一下怎么去转化成最短路(寻找不等式)了。差分约束系统的关键所在也是怎么巧妙地的把复杂的问题用一个简单的模型表现出来,也就是说用数学不等式去等价的约束问题,并且用最短路去求解模型!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: