zoukankan      html  css  js  c++  java
  • 差分约束系统小结

    一、什么是差分约束系统?

    1. 引例

    [x_1-x_0le 2 ......(1) ]

    [x_2-x_0le 7 ......(2) ]

    [x_3-x_0le 8 ......(3) ]

    [x_2-x_0le 3 ......(4) ]

    [x_3-x_2le 2 ......(5) ]

    给定(n)个变量和(m)个不等式,每个不等式形如$x_i - x_j <= a_k $$(0 le i, j < n, 0 le k < m, a_k(已知))(,求)x_{n-1} - x_0(的最大值。例如当)n = 4(,)m = 5(,不等式组如图所示的情况,求)x_3-x_0$的最大值。

    我们考虑暴力。

    1. ((3))(x_3 - x_0 le 8)
    2. ((2)+(5))(x_3-x_0le 9)
    3. ((1)+(4)+(5))(x_3-x_0le 7)

    所以有(x_3-x_0le min{7,8,9}=7),即(x_3-x_0)的最小值为(7)

    当然,对于更大的数据,我们希望能用算法系统地处理,想一想,我们暴力计算的过程跟什么有点像?

    看图,如果让我们求这张图的最短路,我们也许很快就能敲完一份完美的代码。

    再仔细看看这张图,你会发现,这张图似乎与前面的不等式限制条件一一对应。

    没错,我们需要用图论的知识把不等式关系转换成图中点与边之间的关系。

    如若一个系统由(n)个变量和(m)个不等式组成,并且这(m)个不等式对应的系数矩阵中每一行有且仅有一个(1)(-1),其它的都为(0),这样的系统称为差分约束( difference constraints )系统。前面的不等式组同样可以表示成如图的系数矩阵。(摘自夜深人静写算法系列

    [left[ egin{matrix} -1 & 1 & 0 & 0 \ -1 & 0 & 1 & 0 \ -1 & 0 & 0 & 1 \ 0 & -1 & 1 & 0 \ 0 & 0 & -1 & 1 end{matrix} ight] left( egin{matrix} x_0 \ x_1 \ x_2 \ x_3 \ end{matrix} ight) le left( egin{matrix} 2 \ 7 \ 8 \ 3 \ 2 end{matrix} ight) ]

    现在,我们思考如何转换。

    二、代数与图形的关系及原理

    1.原理的感性理解

    对于一个单独的不等式(x_i-x_jle a_k)来说,将这个等式移项,可得(x_ile x_j+a_k),这与单源最短路问题中的(dis[v]le dis[u]+w(u,v))很像啊。

    也就是说,在求最短路时,如果出现(dis[v]>dis[u]+w(u,v)),我们就会更新(dis[v]),力求(dis[v]le dis[u]+w(u,v)),这不与我们求解不等式的解如出同辙吗?

    也就是说,形如(x_i-x_jle a_k)的式子,我们连一条由(j)(i),权值为(a_k)的有向边,然后在图上跑一遍最短路,就能得到我们想要的答案。

    2. 三角不等式

    我们来看一个简化的情况。

    [x_B-x_Ale d_1......(1) ]

    [x_C-x_Ble d_2......(2) ]

    [x_C-x_Ale d_3......(3) ]

    我们同样想知道(x_C-x_A)的最大值,((1)+(2)),可得(x_C-x_Ale d_1+d_2),再有个((3)),所以实质上我们在求(min{(d_1+d_2),d_3}),把这个建个图你就会发现,(min{(d_1+d_2),d_3})正好对应了(A)(C)的最短路。

    3. 解的有无

    因为我们已成功地将不等式关系转化成了图中的关系,所以当图异常结束的时候,就说明原不等式组无解。这里的异常结束包括最短路中的负权环,最长路中的正权环。

    当然,若未更新我们想知道的点的值,就说明它未在差分约束系统里,所有取值都是可行的

    只要最短(长)路算法正常结束并且所有变量都没有确定的值,那么该不等式必然有无限多组解,并且当前所求出的一组解必然是满足题目条件的边界解;一般情况下,我们会新建一个节点值为(0),与其他点相连,有时题目会给出某些初始点的值。

    4. 最大值与最小值的转化

    前面我们有意无意提到了最长路,也就是说,我把前面不等式反号,把(le)变成(ge),照样可以建图,然后跑最长路,思想与前面一样。

    当然,也可以通过数学方法,两边乘个(-1),变成前面的最短路处理。

    三、差分约束的应用&题目

    1. 线性约束:布局Layout

    大意:

    一维线上有(n(2le nle 1000))个点,有些点之间距离不能大于某个数,也有些点之间距离不能小于某个数,求第(n)个点到第(1)个点的距离最小是多少,若没有合法情况,输出(-1),若可以取无限大,输出(-2)

    分析:

    (P_i)表示(i)号位置到(1)的距离,那么我们可以知道,(P_i)单调不下降。

    所以有第一类限制条件:(P_1le P_2le ...le P_{n-1}le P_n)

    同时,它给出了(M_L)(P_i-P_jle D)(M_D)(P_i-P_jge D),直接连边。这里的(P_1)为0,求(P_n)即为最终答案。

    有个坑点,首先应先判断整张图有没有环,即新建节点(0)连向所有节点,这样也不会对后面计算答案产生影响并且可以遍历完整张图。

    然后再从(1)节点跑,若(P_n)未被更新,就说明最短路无限长,否则输出最短路的长度。

    代码:

    #include <queue>
    #include <vector>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define Re register
    using namespace std;
    const int MAX = 1e4 + 5;
    const int INF = 0x7f7f7f7f;
    const int RS = -2139062144;
    inline int read(){
        int f = 1, x = 0; char ch;
        do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9');
        do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); 
        return f * x;
    }
    struct Sakura { int to, nxt, w; }sak[MAX << 1]; int head[MAX], cnt;
    inline void add(int x, int y, int w) {
        ++cnt;
        sak[cnt].to = y, sak[cnt].w = w, sak[cnt].nxt = head[x], head[x] = cnt;
    }
    
    int n, ml, md;
    
    struct SPFA {
    	int dis[MAX], cnt[MAX], vis[MAX];
        inline bool Run(int st) {
        	for (int i = 0;i <= n; ++i) dis[i] = 1e9, cnt[i] = 0, vis[i] = 0;
        	queue <int> Q;
            dis[st] = 0;
            vis[st] = 1;
            Q.push(st);
            while (!Q.empty()) {
                int u = Q.front();
                Q.pop();
                vis[u] = 0;
                ++cnt[u];
                if (cnt[u] >= n) {
                	puts("-1");
                	exit(0);
                }
                for (int i = head[u];i;i = sak[i].nxt) {
                    int v = sak[i].to, w = sak[i].w;
                    if (dis[v] > dis[u] + w) {
                        dis[v] = dis[u] + w;
                        if (!vis[v]) {
                            Q.push(v);
                            vis[v] = 1;
                        }
                    }
                }
            }
            return 1;
        }
    }Spfa;
    
    int main(){
    	n = read(), ml = read(), md = read();
    	for (int i = 1;i <= ml; ++i) {
    		int a = read(), b = read(), d = read();
    		add(a, b, d);
    	}
    	for (int i = 1;i <= md; ++i) {
    		int a = read(), b = read(), d = read();
    		add(b, a, -d);
    	}
    	for (int i = 2;i <= n; ++i) {
    		add(i, i - 1, 0);
    	}
    	for (int i = 1;i <= n; ++i) {
    		add(0, i, 0);
    	}
    	Spfa.Run(0);
    	Spfa.Run(1);
    	if (Spfa.dis[n] == 1e9) {
    		printf("-2");
    		return 0;
    	}
    	else printf("%d", Spfa.dis[n]);
        return 0;
    }
    

    2. 区间约束:Intervals

    大意:

    给定(n)个区间约束条件,要求([a_i,b_i])中至少有(c_i)个数,求构造出的总个数最少。

    分析:

    区间上,设(m_j)表示区间为([j,j])上的个数为多少,即有:(0le m_jle 1),且(sum_{j=a_i}^{b_i}m_jle c_i)

    考虑前缀和(S_j),所以就有:(0le S_j-S_{j-1}le 1)(S_{b_i}-S_{a_i-1}le c_i)

    然后就可以做了。

    #include <queue>
    #include <vector>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define Re register
    using namespace std;
    const int MAX = 200000 + 5;
    const int INF = 0x7f7f7f7f;
    inline int read(){
        int f = 1, x = 0; char ch;
        do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9');
        do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); 
        return f * x;
    }
    struct Sakura { int to, nxt, w; }sak[MAX << 1]; int head[MAX], cnt;
    inline void add(int x, int y, int w) {
        ++cnt;
        sak[cnt].to = y, sak[cnt].w = w, sak[cnt].nxt = head[x], head[x] = cnt;
    }
    
    int t, n, maxx, minn = INF;
    
    struct SPFA {
    	int dis[MAX];
        int vis[MAX]; queue <int> Q;
        inline void Run(int st) {
            for (int i = 0;i <= maxx; ++i) dis[i] = -INF;
            memset(vis, 0, sizeof vis);
            dis[st] = 0;
            vis[st] = 1;
            Q.push(st);
            while (!Q.empty()) {
                int u = Q.front();
                Q.pop();
                vis[u] = 0;
                for (int i = head[u];i;i = sak[i].nxt) {
                    int v = sak[i].to, w = sak[i].w;
                    if (dis[v] < dis[u] + w) {
                        dis[v] = dis[u] + w;
                        if (!vis[v]) {
                            Q.push(v);
                            vis[v] = 1;
                        }
                    }
                }
            }
        }
    }Spfa;
    
    int main(){
    	t = read();
    	while (t --) {
    		n = read();
    		memset(head, 0, sizeof head);
    		cnt = 0;
    		for (int i = 1;i <= n; ++i) {
    			int a = read() + 1, b = read() + 1, w = read();
    			minn = min(minn, a - 1);
    			maxx = max(maxx, b);
    			add(a - 1, b, w);
    		}
    		for (int i = 1;i <= maxx; ++i) add(i - 1, i, 0), add(i, i - 1, -1);
    		Spfa.Run(0);
    		printf("%d
    ", Spfa.dis[maxx]);		
    		if (t != 0) puts("");
    	}
        return 0;
    }
    

    3. 未知约束:出纳员问题

    大意:略

    分析:

    限制条件有点多,慢慢来整理一下。

    (num_i)表示在(i)时刻最多能招到多少人,(R_i)表示在(i)时刻至少需要多少人。

    首先,设(a_i)表示在(i)时刻雇佣的人数,则有(0le a_ile num_i)

    其次,有(a_{i-7}+a_{i-6}+...+a_ige R_i(7le ile 23)),还有(a_0+a_1+...+a_i+a_{i+17}+...+a_{23}ge R_i)((0le i< 7))

    (S_i)(a_i)的前缀和,所以就有:

    1. (0le S_i-S_{i-1}le num[i](0le ile 23))
    2. (S_i-S_{i-8}ge R_i(8le ile 23))
    3. (S_i-S_{i+16}ge R_i-S_{23}(0le i< 8))

    前两个好办,关键是第三个。

    看到我把(S_{23})移到了右边,也许你会猜到什么了吧?

    没错,枚举(S_{23}),从(0)(n)枚举,看是否出现可行解,当然记得要确保图中的节点(23)的值就是你枚举的值,我们可以新建一个值为(0)的节点(h),就有(S_{23}-S_h=)你枚举的值,把它拆成两个不等式,搞定。

    然后,思考,当招(i)个人能满足要求时,(i+1)个人也能满足要求,直接二分求解。

    #include <queue>
    #include <vector>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define Re register
    using namespace std;
    const int MAX = 200000 + 5;
    const int INF = 0x7f7f7f7f;
    inline int read(){
        int f = 1, x = 0; char ch;
        do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9');
        do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); 
        return f * x;
    }
    struct Sakura { int to, nxt, w; }sak[MAX << 1]; int head[MAX], cnt;
    inline void add(int x, int y, int w) {
        ++cnt;
        sak[cnt].to = y, sak[cnt].w = w, sak[cnt].nxt = head[x], head[x] = cnt;
    }
    
    int T, R[MAX], num[MAX],  n;
    
    struct SPFA {
    	int dis[MAX], cnt[MAX], vis[MAX];
        inline bool Run(int st) {
        	memset(dis, 128, sizeof dis);
        	memset(cnt, 0, sizeof cnt);
        	memset(vis, 0, sizeof vis);
        	queue <int> Q;
            dis[st] = 0;
            vis[st] = 1;
            Q.push(st);
            while (!Q.empty()) {
                int u = Q.front();
                Q.pop();
                vis[u] = 0;
                for (int i = head[u];i;i = sak[i].nxt) {
                    int v = sak[i].to, w = sak[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 0;
                        }
                    }
                }
            }
            return 1;
        }
    }Spfa;
    
    int main(){
        T = read();
        while (T --) {
        	bool can = 0;
        	for (int i = 1;i <= 24; ++i) {
        		R[i] = read();
    			if (R[i]) can = 1;
        	}
    		for (int i = 0;i <= 24; ++i) num[i] = 0;
        	n = read();
        	for (int i = 1;i <= n; ++i) {
        		int x = read() + 1;
        		num[x] ++;
        	}
        	if (!can) {
        		printf("0
    ");
        		continue;
        	}
        	bool flag = 0;
        	int l = 0, r = n;
        	while (l < r) {
        		int mid = (l + r) >> 1;
        		memset(head, 0, sizeof head);
        		cnt = 0;
        		for (int i = 9;i <= 24; ++i) add(i - 8, i, R[i]);
        		for (int i = 1;i <= 8; ++i) add(i + 16, i, R[i] - mid);
        		for (int i = 1;i <= 24; ++i) add(i, i - 1, -num[i]), add(i - 1, i, 0);
    			add(0, 24, mid);
    			add(24, 0, -mid);
    			if (Spfa.Run(0)) r = mid, flag = 1;
        		else l = mid + 1;
        	}
        	if (flag) printf("%d
    ", r);
        	else {
        		memset(head, 0, sizeof head);
        		cnt = 0;
         		for (int i = 9;i <= 24; ++i) add(i - 8, i, R[i]);
        		for (int i = 1;i <= 8; ++i) add(i + 16, i, R[i] - r);
        		for (int i = 1;i <= 24; ++i) add(i, i - 1, -num[i]), add(i - 1, i, 0);
    			add(0, 24, r);
    			add(24, 0, -r);			
    			if (Spfa.Run(0)) printf("%d
    ", r);
    			else printf("No Solution
    ");
        	}
    	}
        return 0;
    }
    

    4. 合理转化:糖果

    大意:

    略略略~

    分析:

    把前面弄懂就会做了。

    代码:

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #define ll long long
    #define Re register
    #define Min(a, b) ((a) < (b) ? (a) : (b)) 
    const int MAX = 500000 + 5;
    inline int read(){
        int f = 1, x = 0;char ch;
        do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0'||ch>'9');
        do {x = x*10+ch-'0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); 
        return f*x;
    }
     
    struct sakura {
        int to, nxt, w;
    }sak[MAX]; int head[MAX], cnt;
    inline void add(int x, int y, int w) {
        ++cnt;
        sak[cnt].to = y, sak[cnt].nxt = head[x], sak[cnt].w = w, head[x] = cnt;
    }
    ll ans;
    int n, k, q[MAX], h, t = 1, dis[MAX], vis[MAX], cnts[MAX];
    inline bool SPFA(int st) {
        q[1] = st;
        dis[st] = 0;
        vis[st] = 1;
         
        while (h < t) {
            int u = q[++h];
            cnts[u]++;
            if (cnts[u] > n - 1) return 0;
            vis[u] = 0;
            for (int i = head[u];i;i = sak[i].nxt) {
                int v = sak[i].to, w = sak[i].w;
                if (dis[v] < dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if (!vis[v]) q[++t] = v, vis[v] = 1;
                }
            }
        }
        return 1;
    }
     
    int main(){
        n = read(), k = read();
        for (int i = 1;i <= k; ++i) {
            int x = read(), a = read(), b = read();
            switch(x) {
                case 1:{
                    add(a, b, 0);
                    add(b, a, 0);
                    break;
                } 
                case 2:{
                    if (a == b) {
                        puts("-1");
                        return 0;
                    }
                    add(a, b, 1);
                    break;
                }
                case 3:{
                    add(b, a, 0);
                    break;
                }
                case 4:{
                    if (a == b) {
                        puts("-1");
                        return 0;
                    }
                    add(b, a, 1);
                    break;
                }
                case 5:{
                    add(a, b, 0);
                    break;
                }
            }
        }
        for (int i = n;i >= 1; --i) {
            add(n + 1, i, 1);
        }
        if (SPFA(n + 1)) {
            for (int i = 1;i <= n; ++i) {
                ans += dis[i];
            }
            printf("%lld", ans);
        }
        else printf("-1");
        return 0;
    }
    
  • 相关阅读:
    android 项目
    input keyevent 数字对应的操作
    logcat 使用方法
    android查看内存使用情况
    图片点击放大效果
    禁止img图片拖动在新窗口打开
    人工智能
    游戏开发
    随手做的一个模拟弹出窗口
    Html的<meta>标签使用方法及用例
  • 原文地址:https://www.cnblogs.com/silentEAG/p/11431902.html
Copyright © 2011-2022 走看看