有关差分约束系统

差分约束系统详解(极力推荐)==> http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html

个人瞎想 : 差分约束系统的题最重要的就是充分利用题目条件建立模型、构造出不等式最后使用最短路来算出答案,当然有些题目即使构造出了若干不等式我们可能只得到了部分约束性条件,真正的答案可能需要枚举或者二分枚举。在构造不等式的时候要充分考虑模型的实际意义,以防建少了边亦或者建多了边导致答案错误。

题目:

POJ 3159 Candies

题意 : 发一些糖果,其中他们之间的糖果数目有一定的约束关系,u,v,w 表示a[v]<=a[u]+w,求a[n]-a[1]的最大值

分析 : 裸题、对于每一个约束条件建 a[u] ---> a[v] 且边权为 w 的边然后跑最短路即可

#include<queue>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int maxn = 3e4 + 10;
const int maxm = 2e5 + 10;
const int INF  = 0x3f3f3f3f;
typedef pair<int, int> pii;
struct EDGE{ int v, w, nxt; };

EDGE Edge[maxm];
int N, M, Dis[maxn], Head[maxn], cnt;

inline void init()
{
    for(int i=0; i<=N; i++)
        Head[i] = -1;
    cnt = 0;
}

inline void AddEdge(int from, int to, int weight)
{
    Edge[cnt].v = to;
    Edge[cnt].w = weight;
    Edge[cnt].nxt = Head[from];
    Head[from] = cnt++;
}

int Dijkstra(int st)
{
    for(int i=1; i<=N; i++)
        Dis[i] = INF;
    Dis[st] = 0;

    priority_queue< pii, vector<pii>, greater<pii> > Heap;
    Heap.push(make_pair(0, st));

    pii T;
    while(!Heap.empty()){
        T = Heap.top(); Heap.pop();
        if(Dis[T.second] != T.first) continue;
        for(int i=Head[T.second]; i!=-1; i=Edge[i].nxt){
            int Eiv = Edge[i].v;
            if(Dis[Eiv] > Dis[T.second] + Edge[i].w){
                Dis[Eiv] = Dis[T.second] + Edge[i].w;
                Heap.push(make_pair(Dis[Eiv], Eiv));
            }
        }
    }
    return Dis[N];
}

int main(void)
{
    while(~scanf("%d %d", &N, &M)){
        init();
        int from, to, weight;
        while(M--){
            scanf("%d %d %d", &from, &to, &weight);
            AddEdge(from, to, weight);
        }printf("%d
", Dijkstra(1));
    }
    return 0;
}
View Code

POJ 3169 Layout

题意 : 一些母牛按序号排成一条直线。有两种要求,A和B距离不得超过X,还有一种是C和D距离不得少于Y,问可能的最大距离。如果没有输出-1,如果可以随便排输出-2,否则输出最大的距离。

分析 : 裸题、建边的时候注意需要将不等式的符号全部搞成同向的,-1的情况对应两点间不可达、-2的情况对应两点间有负环的存在(这个负环的情况可以自己造个小例子,然后根据题目的实际意义看看到底发生了什么情况!)

#include<queue>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int maxn = 1e3 + 10;
const int maxm = 2e4 + 10;
const int INF  = 0x3f3f3f3f;
typedef pair<int, int> pii;
struct EDGE{ int v, w, nxt; };

EDGE Edge[maxm];
int N, M1, M2, Dis[maxn], Head[maxn], cnt;

inline void init()
{
    for(int i=0; i<=N; i++)
        Head[i] = -1;
    cnt = 0;
}

inline void AddEdge(int from, int to, int weight)
{
    Edge[cnt].v = to;
    Edge[cnt].w = weight;
    Edge[cnt].nxt = Head[from];
    Head[from] = cnt++;
}

bool vis[maxn];
int PushCnt[maxn];
bool SPFA(int st)///若要判断负环、改为 bool
{
    for(int i=1; i<=N; i++)
        vis[i] = false, PushCnt[i] = 0, Dis[i] = INF;
    deque<int> que;
    que.push_back(st);
    vis[st]=true;
    Dis[st]=0;
    while (!que.empty())
    {
        int T=que.front(); que.pop_front();
        vis[T]=false;
        for (int i=Head[T]; i!=-1; i=Edge[i].nxt)
        {
            int v=Edge[i].v;
            int w=Edge[i].w;
            if (Dis[v]>Dis[T]+w){
                Dis[v]=Dis[T]+w;
                if (!vis[v]){
                    if(++PushCnt[v] > N) return false; //有负环
                    vis[v]=true;
                    if(!que.empty() && Dis[v] < Dis[que.front()]) que.push_front(v);
                    else que.push_back(v);
                }
            }
        }
    }return true;
}

int main(void)
{
    while(~scanf("%d %d %d", &N, &M1, &M2)){
        init();
        int from, to, weight;
        while(M1--){
            scanf("%d %d %d", &from, &to, &weight);
            AddEdge(from, to, weight);
        }
        while(M2--){
            scanf("%d %d %d", &from, &to, &weight);
            AddEdge(to, from, -weight);///保证差分约束系统中不等号的方向一致
        }
        if(!SPFA(1)) puts("-1");
        else if(Dis[N] == INF) puts("-2");
        else printf("%d
", Dis[N]);
    }
    return 0;
}
View Code

POJ 2175 Cashier Employment

题意 : 在一家超市里,每个时刻都需要有营业员看管,R(i)  (0 <= i < 24)表示从i时刻开始到i+1时刻结束需要的营业员的数目,现在有N(N <= 1000)个申请人申请这项工作,并且每个申请者都有一个起始工作时间 ti,如果第i个申请者被录用,那么他会连续工作8小时。现在要求选择一些申请者进行录用,使得任何一个时刻i,营业员数目都能大于等于R(i)。

分析 : 炒鸡难的一道差分约束论文题( 冯威论文——《数与图的完美结合》),具体的话参考 ==> 博客   Orz

#include<queue>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxm = 1e3 + 10;
const int maxn = 26;
const int INF  = 0x3f3f3f3f;
struct EDGE{ int v, w, nxt; };

EDGE Edge[maxm];
int Head[maxn], cnt;
int r[maxn], t[maxn];
int ans;

inline void init()
{
    for(int i=0; i<26; i++)
        Head[i] = -1;
    cnt = 0;
}

inline void AddEdge(int from, int to, int weight)
{
    Edge[cnt].v = to;
    Edge[cnt].w = weight;
    Edge[cnt].nxt = Head[from];
    Head[from] = cnt++;
}

inline void BuildMap(int x)//注意这里建图的话,将分析中的不等号反过来建了,将原本的求最长路变成了求最短路
{
    init();
    for(int i=1; i<=24; i++){//这24条边是必须的!
        AddEdge(i-1, i, t[i]);
        AddEdge(i, i-1, 0);
    }
    for(int i=1; i<=16; i++) AddEdge(i+8, i, -r[i+8]);
    for(int i=17;i<=24; i++) AddEdge(i-16,i,x-r[i-16]);
    AddEdge(24, 0, -x);
}

bool SPFA(int x)
{
    int Dis[maxn], PushCnt[maxn];
    bool vis[maxn];
    for(int i=0; i<26; i++)
        Dis[i] = INF, PushCnt[i] = 0, vis[i] =false;
    Dis[0] = 0;
    PushCnt[0] = 1;
    vis[0] = true;
    deque<int> que;
    que.push_back(0);
    while(!que.empty()){
        int T = que.front(); que.pop_front();
        vis[T] = false;
        for(int i=Head[T]; i!=-1; i=Edge[i].nxt){
            int Eiv = Edge[i].v;
            if(Dis[Eiv] > Dis[T] + Edge[i].w){
                Dis[Eiv] = Dis[T] + Edge[i].w;
                if(!vis[Eiv]){
                    if(++PushCnt[Eiv] > 24) return false;
                    vis[Eiv] = true;
                    if(!que.empty() && Dis[Eiv] < Dis[que.front()]) que.push_front(Eiv);
                    else que.push_back(Eiv);
                }
            }
        }
    }
    return true;
}

inline void BinSearch(int L, int R)
{
    int mid;
    while(!(L > R)){
        mid = L + ((R-L)>>1);
        BuildMap(mid);//对于每一个sum都要重建一次图
        if(SPFA(mid)) ans=mid, R = mid-1;
        else L = mid+1;
    }
}

int main(void)
{
    int nCase, n;
    scanf("%d", &nCase);
    while(nCase--){
        for(int i=0; i<26; i++) t[i] = 0;
        for(int i=1; i<=24; i++)
            scanf("%d", &r[i]);//某个小时需要多少人
        scanf("%d", &n);
        int tmp;
        for(int i=1; i<=n; i++){
            scanf("%d", &tmp);
            t[tmp+1]++;//在tmp+1(全体+1,即0~23变成了1~24)小时的应聘人数
        }
        ans = -INF;
        BinSearch(0, n);
        if(ans == -INF) puts("No Solution");
        else printf("%d
", ans);
    }
    return 0;
}
View Code

POJ 1201 Interval

题意 : 给定n(n <= 50000)个整点闭区间和这个区间中至少有多少整点需要被选中,每个区间的范围为[ai, bi],并且至少有ci个点需要被选中,其中0 <= ai <= bi <= 50000,问[0, 50000]至少需要有多少点被选中。

分析 :  (引用了开头博客地址的分析)这类问题就没有线性约束那么明显,需要将问题进行一下转化,考虑到最后需要求的是一个完整区间内至少有多少点被选中,试着用d[i]表示[0, i]这个区间至少有多少点能被选中,根据定义,可以抽象出 d[-1] = 0,对于每个区间描述,可以表示成d[ bi ]  - d[ ai - 1 ] >= ci,而我们的目标要求的是 d[ 50000 ] - d[ -1 ] >= T 这个不等式中的T,将所有区间描述转化成图后求-1到50000的最长路。这里忽略了一些要素,因为d[i]描述了一个求和函数,所以对于d[i]和d[i-1]其实是有自身限制的,考虑到每个点有选和不选两种状态,所以d[i]和d[i-1]需要满足以下不等式:  0 <= d[i] - d[i-1] <= 1   (即第i个数选还是不选)

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 5e4 + 10;
const int N    = 5e4 + 1;
const int INF  = 0x3f3f3f3f;
struct EDGE{ int v, w, nxt; };

int Head[maxn], cnt, MaxN;
EDGE Edge[maxn+2*maxn];

inline void init()
{
    for(int i=1; i<=N; i++)
        Head[i] = -1;
    cnt = 0;
    MaxN = -INF;
}

inline void AddEdge(int from, int to, int weight)
{
    Edge[cnt].v = to;
    Edge[cnt].w = weight;
    Edge[cnt].nxt = Head[from];
    Head[from] = cnt++;
}

bool vis[maxn];
int Dis[maxn];
inline void SPFA(int st)
{
    for(int i=0; i<=MaxN; i++)
        Dis[i] = -INF, vis[i] = false;
    Dis[st] = 0;
    vis[st] = true;
    deque<int> que;
    que.push_back(st);
    while(!que.empty()){
        int T = que.front(); que.pop_front();
        vis[T] = false;
        for(int i=Head[T]; i!=-1; i=Edge[i].nxt){
            int Eiv = Edge[i].v;
            if(Dis[Eiv] < Dis[T] + Edge[i].w){
                Dis[Eiv] = Dis[T] + Edge[i].w;
                if(!vis[Eiv]){
                    vis[Eiv] = true;
                    que.push_back(Eiv);
                }
            }
        }
    }
}
int main(void)
{
    int m;
    while(~scanf("%d", &m)){
        init();
        int from, to, weight;
        while(m--){
            scanf("%d %d %d", &from, &to, &weight);
            AddEdge(from, to+1, weight);
            MaxN = max(to+1, MaxN);
        }
        for(int i=1; i<=MaxN; i++){
            AddEdge(i, i+1, 0);
            AddEdge(i+1, i, -1);
        }
        SPFA(1);
        printf("%d
", Dis[MaxN]);
    }
    return 0;
}
View Code

备注 : 实际这题也可以贪心,只要把需要(也就是缺的)选择的数字放在每个区间的最后,就能贪心的保证后面的区间能最大可能的利用前面区间选择的数字,开始按照每个区间的区间尾从小到大排序,然后对每个区间用贪心算法,但是每次要查询这个区间已经选择的数字数目够不够了,这个查询开始就是做简单的查询,发现老是超时,因为每次查询可能需要大量的时间,后来用线段树将查询优化了一下,就AC了,如果发现区间数字不够那么就从区间尾部寻找没选择的数字选中即可,同时更新线段树就OK 了!

#include<bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
const int maxn = 5e4 + 11;
struct Interval{
    int L, R, Need;
    bool operator < (const Interval &rhs)const{
        if(this->R == rhs.R) return this->L > rhs.L;
        return this->R < rhs.R;
    };
}arr[maxn];
bool Have[maxn];
int sum[maxn<<2], add[maxn<<2];

inline void PushUp(int rt){ sum[rt] = sum[rt<<1] + sum[rt<<1|1]; }
inline void PushDown(int rt, int m)
{
    if(add[rt]){
        add[rt<<1] += add[rt];
        add[rt<<1|1] += add[rt];
        sum[rt<<1] += add[rt]*(m-(m>>1));
        sum[rt<<1|1] += add[rt]*(m>>1);
        add[rt] = 0;
    }
}

inline void update(int L, int R, int c, int l, int r, int rt)
{
    if(L <= l && r <= R){
        add[rt] += c;
        sum[rt] += c * (r-l+1);
        return;
    }
    PushDown(rt, r-l+1);
    int m = (l+r)>>1;
    if(L <= m) update(L, R, c, lson);
    if(m <  R) update(L, R, c, rson);
    PushUp(rt);
}

int Query(int L, int R, int l, int r, int rt)
{
    if(L <= l && r <= R) return sum[rt];
    PushDown(rt, r-l+1);
    int m = (r+l)>>1;
    int ret = 0;
    if(L <= m) ret += Query(L, R, lson);
    if(m  < R) ret += Query(L, R, rson);
    return ret;
}

int main(void)
{
    int n;
    while(~scanf("%d", &n)){
        memset(Have, false, sizeof(Have));
        memset(sum, 0, sizeof(sum));
        memset(add, 0, sizeof(add));
        for(int i=1; i<=n; i++)
            scanf("%d %d %d", &arr[i].L, &arr[i].R, &arr[i].Need),
            arr[i].L++, arr[i].R++;
        sort(arr+1, arr+1+n);
        int ans = 0;
        for(int i=1; i<=n; i++){
            int num = Query(arr[i].L, arr[i].R, 1, 50001, 1);
            if(num < arr[i].Need){
                int Supp = arr[i].Need - num;
                ans += Supp;
                for(int j=arr[i].R; j>=arr[i].L; j--){
                    if(!Have[j]) { Have[j]=true; update(j, j, 1, 1, 50001, 1); Supp--; }
                    if(Supp==0) break;
                }
            }
        }
        printf("%d
", ans);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/qwertiLH/p/7820244.html