zoukankan      html  css  js  c++  java
  • 差分约束

    放一篇写的很好的博客:http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html

    个人感觉我差分约束还是不是很熟。

    先总结一下遇见的差分约束题目类型:

    1. 给定m条约束关系,求可行解

    这种题目是需要建一个超级源点,向每个点连一条权值为0的边,之后v,u建有向边,跑最短最长都可以。

    最后0到各个点的dis就是可行解

    2. 给定m条约束关系,求[n] - [1]最大最小值。

    这种类型就不需要超源,直接v,u建有向边,最大值跑最短路,最小值跑最长路

    3. 给定m条约束关系,求[n] - [1]解的情况。

    求最大值的最短路无解是有负环,最小值的最长路是正环。dis[n]为INF or -1 时无数解。

    4. 给定m条约束关系,求未知量最值的情况。

    未知条件约束是指在不等式的右边不一定是个常数,可能是个未知数,可以通过枚举这个未知数,然后对不等式转化成差分约束进行求解。(一般可以二分)

    放几个自己需要注意的点:

    1.保证有解就可以考虑dij

    2.求最大值的最短路无解是有负环,最小值的最长路是正环。dis[n]为INF or -1 时无数解。

    3. 小于号与小于等于号的转换。

    4. 约束条件为a - b <= c时,建有向边b-a;为 >=时,建有向边a-b

    P5960 【模板】差分约束算法

    模板题。就是题型1的解法。

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    using namespace std;
    typedef long long ll;
    const int INF = 0x3f3f3f3f; // 2122219134
    const int maxn = 5005 + 10;
    const int maxm = 1e4 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    
    int tot,n,m;
    int head[maxn],  dis[maxn], cnt[maxn];
    int vis[maxn];
    
    
    void init(){
        memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    bool bfs_spfa(int s) {
        memset(dis, INF, sizeof(dis));//初始化dis
        queue<int> q; q.push(s);
        vis[s] = 1, dis[s] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to, w = edge[i].w;
                if(dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
                        if (++cnt[v] >= n)  return 1;
                    }
                }
            }
        }
        return 0;
    }
    
    int main() {
        init();
        int u, v, w, op;
        cin >> n >> m;
        while (m--) {
            cin >> u >> v >> w;
            add(v, u, w);
        }
        for (int i = 1; i <= n; ++i) add(0, i, 0);
        if (bfs_spfa(0)) cout << "NO" << endl;
        else for (int i = 1; i <= n; ++i) cout << dis[i] << " ";
        return 0;
    }
    View Code

    K - Candies  POJ - 3159

    题意:分糖,n个小朋友,m个关系,AB间关系的意思是A的糖果最多比B少w个,w为关系的权。

    求1号和n号最多能差多少糖果。

    (题型2)这道题的关系是B-A<=W,所以就是AB建有向边跑最短路即可。

    解法:这道题卡一般的SPFA,但是由于题目保证有解,所以我们就可以直接用堆优化dij来跑。

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x3f3f3f3f; // 2122219134
    const int maxn = 3e4 + 10;
    const int maxm = 2e5 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    
    int tot,n,m;
    int head[maxn],  dis[maxn], cnt[maxn];
    int vis[maxn];
    
    void init(){
        memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    
    void dijkstra(int s)
    {
        memset(dis, INF, sizeof(dis));//初始化dis
        priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大
        dis[s] = 0; q.push({dis[s],s});
        while(!q.empty()) {
            int now = q.top().second;
            q.pop();
            if(vis[now]) continue;
            vis[now] = 1;
            for(int i = head[now]; i != -1; i = edge[i].nex) {
                int v = edge[i].to;
                if(dis[v] > dis[now] + edge[i].w) {
                    dis[v] = dis[now] + edge[i].w;
                    q.push({dis[v],v});
                }
            }
        }
    }
    
    
    int main() {
        init();
        int u, v, w, op;
        scanf("%d%d", &n, &m);
        while (m--) {
            scanf("%d%d%d", &u, &v, &w);
            add(u, v, w);
        }
        dijkstra(1);
        cout << dis[n] << endl;
        return 0;
    }
    View Code

    S - Layout  POJ - 3169

    差分约束条件:

    • b <= a + L 奶牛 A 和奶牛 B 至多相隔 L 的距离。
    • a <= b - D 奶牛 A 和奶牛 B 至少相隔 D 的距离。
    • si <= si+1 后一头奶牛的距离大于前一头
    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x3f3f3f3f; // 2122219134
    const int maxn = 3e4 + 10;
    const int maxm = 2e5 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    
    int tot,n,m1,m2;
    int head[maxn],  dis[maxn], cnt[maxn];
    int vis[maxn];
    
    void init(){
        memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    bool bfs_spfa(int s) {
        memset(dis, INF, sizeof(dis));//初始化dis
        queue<int> q; q.push(s);
        vis[s] = 1, dis[s] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to, w = edge[i].w;
                if(dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
                        if (++cnt[v] >= n)  return 1;
                    }
                }
            }
        }
        return 0;
    }
    
    int main() {
        init();
        int u, v, w;
        scanf("%d%d%d", &n, &m1,&m2);
        while (m1--) {
            scanf("%d%d%d", &u, &v, &w);
            add(u, v, w);
        }
        while (m2--) {
            scanf("%d%d%d", &u, &v, &w);
            add(v, u, -w);
        }
        for (int i = 1; i <= n; ++ i) add(i+1, i, 0);
        if (bfs_spfa(1)) cout << -1 << endl;
        else {
            if (dis[n] > INF / 2) cout << -2 << endl;
            else cout << dis[n] << endl;
        }
        return 0;
    }
    View Code

    POJ - 1201

    非常经典的区间差分约束的题目。以下来自上述博客:

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

          例如3 6 2 表示[3, 6]这个区间至少需要选择2个点,可以是3,4也可以是4,6(总情况有 C(4, 2)种 )。

          这类问题就没有线性约束那么明显,需要将问题进行一下转化,考虑到最后需要求的是一个完整区间内至少有多少点被选中,试着用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个数选还是不选)

          这样一来,还需要加入 50000*2 = 100000 条边,由于边数和点数都是万级别的,所以不能采用单纯的Bellman-Ford ,需要利用SPFA进行优化,由于-1不能映射到小标,所以可以将所有点都向x轴正方向偏移1个单位(即所有数+1)。

    但是这里这道题是从1开始的,所以就不用下标偏移了。
    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x3f3f3f3f; // 2122219134
    const int maxn = 5e4 + 10;
    const int maxm = 5e5 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    
    int tot,n,m1,m2;
    int head[maxn],  dis[maxn], cnt[maxn];
    int vis[maxn];
    
    void init(){
        memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    bool bfs_spfa(int s) {
        memset(dis, -INF, sizeof(dis));//初始化dis
        queue<int> q; q.push(s);
        vis[s] = 1, dis[s] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to, w = edge[i].w;
                if(dis[v] < dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
                        if (++cnt[v] >= n)  return 1;
                    }
                }
            }
        }
        return 0;
    }
    
    int main() {
        init();
        int u, v, w;
        scanf("%d", &n);
        for (int i = 0; i <= 50000; i++) {
            add(i, i+1, 0);
            add(i+1, i, -1);
        }
        for (int i = 1; i <= n; i++) {
            scanf("%d%d%d", &u, &v, &w);
            add(u-1, v, w);
        }
        bfs_spfa(0);
        printf("%d\n", dis[50000]);
    
        return 0;
    }
    View Code

    POJ - 1364

    这道题主要就是考 > 转化为 >=。

    题面好长。我是看题解的。

    这里因为0是有用过的所以就选择n+1作为超源。

    因为0到n+1总共有n+2个点,所以spfa里的入队次数要改成>=n+2

    题目大意;
    已知一个序列a[1], a[2], ……, a[n],给出它的若干子序列以及对该子序列的
    约束条件,例如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”。

    解题思路:
    s[a] + s[a+1] + …… + s[b] < c 可以转化成前n项和sum[b] - sum[a - 1] < c,
    为了能用Bellman_Ford,即将< 转化成 <= ,sum[b] - sum[a - 1] <= c - 1。
    转化为最长路或者最短路来判断是否有解都可以。

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x3f3f3f3f; // 2122219134
    const int maxn = 5e4 + 10;
    const int maxm = 5e5 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    
    int tot,n,m;
    int head[maxn],  dis[maxn], cnt[maxn];
    int vis[maxn];
    
    void init(){
        memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    bool bfs_spfa(int s) {
        memset(dis, INF, sizeof(dis));//初始化dis
        queue<int> q; q.push(s);
        vis[s] = 1, dis[s] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to, w = edge[i].w;
                if(dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
                        if (++cnt[v] > n+2)  return 1;
                    }
                }
            }
        }
        return 0;
    }
    
    int main() {
        while(~scanf("%d",&n)&&n) {
            scanf("%d",&m);
            init();
            for(int i=0;i<m;++i) {
                int a,b,c;
                string op;
                cin>>a>>b>>op>>c;
                if (op[0]=='g') add(a+b,a-1,-c-1);
                else add(a-1,a+b,c-1);
            }
            for (int i=0;i<=n;++i) add(n+1,i,0);
            if (!bfs_spfa(n+1)) printf("lamentable kingdom\n");
            else printf("successful conspiracy\n");
        }
        return 0;
    }
    View Code

    Integer Intervals

    题意:给定n个区间,你需要挑选出一个集合,使得集合中的元素与每个区间的交集>=2,输出集合元素的最小数量。

    题解:因为求最小值,所以用最长路。最长路需要把约束条件换成 >=。建边见Note4.

    约束条件: B - A >= 2

         A - A-1 <= 1

           A-1 - A >= 0

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x3f3f3f3f; // 2122219134
    const int maxn = 5e4 + 10;
    const int maxm = 5e5 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    int maxx;
    int tot,n,m;
    int head[maxn],  dis[maxn], cnt[maxn];
    int vis[maxn];
    
    void init(){
        memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    bool bfs_spfa(int s) {
        memset(dis, -INF, sizeof(dis));//初始化dis
        queue<int> q; q.push(s);
        vis[s] = 1, dis[s] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to, w = edge[i].w;
                if(dis[v] < dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
    //                    if (++cnt[v] >= maxx)  return 1;
                    }
                }
            }
        }
        return 0;
    }
    
    int main() {
        while (~scanf("%d",&n)) {
            init();
            maxx = 0;
            for (int i = 1, u, v, w; i <= n; ++i) {
                scanf("%d%d", &u, &v);
                add(u, v+1, 2);
                maxx = max(v+1, maxx);
            }
            for (int i = 0; i < maxx; ++ i) {
                add(i+1, i, -1), add(i, i+1, 0);
            }
            bfs_spfa(0);
            cout << dis[maxx] << endl;
        }
        return 0;
    }
    View Code

    House Man

    题意:有N个在一条直线上的房子, 每个房子有着不同的高度, 一个超人可以将这些房子左右移动但不能改变房子之间的相对位置.现在超人要从最矮的房子跳到刚好比他高的房子上面, 且每次跳的房子都要比当前房子要高.那么最后超人肯定会跳到最高的房子上面, 现在给出超人能够跳的最远距离, 问: 如何摆放这些房子, 使得超人能够经过所有的房子跳到最高的房子, 又要使最矮的房子和最高的房子之间的距离最远

    这里有几个需要注意的点。

    首先约束条件是:B - A <= D (在按高度升序排列下,B的标号要大于A的标号)其实关于这点我不是特别明白,似乎是一个贪心

            A+1 - A <= -1 (房子位置不重合)

    UPD:

    1.两个相邻点之间最小距离为一,则有d[i+1]-d[i]>=1,等价于 d[i]-d[i+1]<=-1;

    2.高度最接近的两个点的最大距离为m,按距离排序之后可有 d[j+1]-d[j]<=m; 查分约束系统的条件找出来了;

    剩下就是构图的事了。

    由的d[i]-d[i+1]<=-1 的编辑条件可知,我们给各个点制定了一个方向,就是从左到右是依次增大的。

    这就限定了的d[j+1]-d[j]<=m的连边条件,必须是由对应的序号小的点连向序号大的点,才对应了题目中的swap操作。

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x7fffffff; // 这里要改得大一些,虽然我的0x3f3f3f3f也过了
    const int maxn = 5e4 + 10;
    const int maxm = 5e5 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    int maxx;
    int tot,n,m;
    int head[maxn],  dis[maxn], cnt[maxn];
    int vis[maxn];
    
    void init(){
        memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    bool bfs_spfa(int s) {
    //    memset(dis, -INF, sizeof(dis));//初始化dis
        for (int i = 1; i <= 1000; ++ i) dis[i] = INF;
        queue<int> q; q.push(s);
        vis[s] = 1, dis[s] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to, w = edge[i].w;
                if(dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
                        if (++cnt[v] >= n)  return 1;
                    }
                }
            }
        }
        return 0;
    }
    struct House {
        int id, height;
    }house[maxn];
    int main() {
        int T; scanf("%d", &T);
        for (int Ti = 1; Ti <= T; ++ Ti) {
            int d;
            scanf("%d%d", &n, &d);
            init();
            for (int i = 1, w; i <= n; ++i) {
                scanf("%d", &w);
                house[i].id = i, house[i].height = w;
                if (i != n) add(i+1,i,-1);
            }
            sort(house+1, house+1+n, [](House a, House b){return a.height < b.height;});
            for (int i = 1; i < n; ++ i) {
                int u = house[i].id;
                int v = house[i+1].id;
                if (u > v) swap(u, v);
                add(u, v, d);
            }
            int s = house[1].id;
            int t = house[n].id;
            if (s > t) swap(s, t);
            if (bfs_spfa(s)) dis[t] = -1;
            printf("Case %d: %d\n", Ti, dis[t]);
        }
        return 0;
    }
    View Code

    THE MATRIX PROBLEM

    给你一个N*M的矩阵,给你两个数L和U(L <= U)问你是否存在这样的N+M个数字(计作A1….AN, B1…..BM),

    使矩阵中任意元素Xij,满足:

    L <= (Xij * Ai) / Bj <= U

    题解:

    差分约束,这道题构造差分约束方程特别巧妙。

    转换成:Xij * Ai - U * Bj <= 0

    L * Bj - Xij * Ai <= 0

    由于差分约束中xi - xj <= val 方程的xi与xj前面不能有系数。

    利用log运算将乘法转换成为加法,成为了标准的差分约束方程。

    注意:

    判断有无解(负环)的时候,如果用spfa,不能用入队次数大于N来判断,会超时。

    有如下两种比较可靠的方法(一般情况下)

    1:某个点入队次数大于sqrt(N)的时候

    2:所有入队次数大于T * (N + M),其中T一般取2

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    #pragma GCC optimize(2)
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x3f3f3f3f;
    const int maxn = 1e5 + 10;
    const int maxm = 5e5 + 10;
    struct node {
        int to;
        double w;
        int nex;
    }edge[maxm];
    int tot, n, m;
    int head[maxn];
    double dis[maxn];
    void init(){
        memset(head, -1, sizeof(head));
        tot = 0;
    }
    
    void add(int u, int v, double w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    
    bool bfs_spfa(int s) {
        int vis[maxn] = {0};
        int cnt[maxn] = {0};
        for (int i = 1; i <= n+m; ++ i) dis[i] = INF;
        queue<int> q; q.push(s);
        vis[s] = 1, dis[s] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to;
                double w = edge[i].w;
                if (dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
                        if (++cnt[v] >= (n+m+1))  return 1;
                    }
                }
            }
        }
        return 0;
    }
    
    int main(void){
        double L, U;
        while(~scanf("%d%d%lf%lf",&n,&m,&L,&U)){
            init();
            L = log2(L);U = log2(U);
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    double tp;scanf("%lf",&tp);
                    tp = log2(tp);
                    int x = i,y = j+n;
                    add(x,y,tp-L);
                    add(y,x,U-tp);
                }
            }
            if (!bfs_spfa(1)) printf("YES\n");
            else printf("NO\n");
        }
        return 0;
    }
    View Code

    Cashier Employment

    这题非常好!

    题意:有一个超市需要一些出纳员,已给出这个超市在各个时间段(0-1,1-2,2-3...共24个时间段)至少需要的出纳员数目,
    现在前来应聘有n个人,每个人都有一个固定的开始工作的时间,这也意味着从这个时间开始他要连续工作8个小时。在满足这
    个超市对出纳员的需求的前提下,让你求出最少雇佣的出纳员人数。

    need[i]表示在第 i 个小时至少也要的人数,work[i]表示应聘者中可以在第i个小时开始工作的人数,s[i]表示前i个小时雇佣的人数,

    x[ i ]表示第 i 个小时雇佣的人数。 s[i] - s[i - 1] = x[i]

    约束条件:
    (1) 0<= x[i] <= x[ i ] ,转化为 0 <= s[ i ] - s[i - 1] <= work[ i ]
    (2) i >= 8 时:need[ i ] <= x[i] + x[i - 1] + x[i - 2] + x[i - 3] + x[i - 4] + x[i - 5] + x[i - 6] + x[i - 7]
              转化为 need[ i ] <= s[ i ] - s[i - 8]
              i < 8 时:s[ i ] +s[ 24 ] -s[16 + i] >= need[i] (不清楚的可以模拟一下)
    (3)对上面的S[24]我们不知道它的值,但我们知道它表示前24个小时所雇用的总人数,也就是我们要求的结果sum.因此对于未知
              的sum,我们需要从0到n枚举sum。需要再建一条边即:s[24] - s[0] >= sum

    #include<stdio.h>
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<cmath>
    #include<string>
    #include<map>
    #include<queue>
    #pragma GCC optimize(2)
    using namespace std;
    #define pii pair<int,int>
    typedef long long ll;
    const int INF = 0x3f3f3f3f;
    const int maxn = 1e3 + 10;
    const int maxm = 2e5 + 10;
    struct node {
        int to;
        int w;
        int nex;
    }edge[maxm];
    int tot, n, m;
    int head[maxn];
    int dis[maxn];
    int work[maxn] = {0};//work[i]表示在第 i 个小时开始工作的人数
    int need[maxn];//应聘者中可以在第i个小时开始工作的人数
    
    void init(){
        memset(head, -1, sizeof(head));
        tot = 0;
    }
    
    void add(int u, int v, int w){
        edge[tot] = {v, w, head[u]};
        head[u] = tot++;
    }
    
    void getmap(int sum){
        for(int i = 1; i <= 24; ++i){
            add(i - 1, i, 0);
            add(i, i - 1, -work[i]);
            if(i >= 8)
                add(i - 8, i, need[i]);
            else
                add(16 + i, i, need[i] - sum);
        }
        add(0, 24, sum);
    }
    
    bool bfs_spfa(int sum) {
        int vis[maxn] = {0};
        int cnt[maxn] = {0};
        for (int i = 1; i <= 24; ++ i) dis[i] = -INF;
        queue<int> q; q.push(0);
        vis[0] = 1, dis[0] = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            vis[u] = 0;
            for (int i = head[u]; ~i; i = edge[i].nex){
                int v = edge[i].to;
                int w = edge[i].w;
                if (dis[v] < dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) {
                        q.push(v), vis[v] = 1;
                        if (++cnt[v] >= 24)  return 0;
                    }
                }
            }
        }
        return dis[24] == sum;;
    }
    
    int main (){
        int T;
        int OK, st;
        scanf("%d", &T);
        while(T--){
            OK = 0;
            memset(need, 0, sizeof(need));
            memset(work, 0, sizeof(need));
            for(int i = 1; i <= 24; ++i)
                scanf("%d", &need[i]);
            scanf("%d", &n);
            for(int i = 0; i < n; ++i){
                scanf("%d", &st);
                work[st + 1]++;
            }
            for(int i = 0; i <= n; ++i){
                init();
                getmap(i);
                if(bfs_spfa(i)){
                    OK = 1;
                    printf("%d\n", i);
                    break;
                }
            }
            if(!OK)
                printf("No Solution\n");
        }
        return 0;
    }
    View Code
    写了这么多差分约束我发现。
    差分约束不过就是找到所有的约束条件,再映射到最短路算法上就可以求解了。
    但最难的地方就是归纳题意,找到约束条件。
  • 相关阅读:
    Java之Chat历程
    Java之静态方法中的内部类
    Java异常捕获之finally
    C语言复杂声明的本质与局限
    使用beyond compare或kompare作为git的对比、合并工具
    [二分] [洛谷] P1258 小车问题
    [STL] [洛谷] P1165 日志分析
    [洛谷] P2802 回家
    卡特兰数的应用
    [洛谷] P1722 矩阵Ⅱ
  • 原文地址:https://www.cnblogs.com/Vikyanite/p/13552453.html
Copyright © 2011-2022 走看看