• POJ 图算法(2)


     

     

    图算法

     

     

    差分约束系统的建立和求解

    poj1201(1716类似),poj2983, poj3159

    poj1275, poj1364

    最小(大)费用最大流

    poj2516, poj2195, poj3422(最大费用最大流) 

    poj2135(很裸的最小费用最大流,不过有一个大坑。。。) 

    双连通分量

    poj2942poj3694

    强连通分支及其缩点

    poj2186, poj3592, poj3114

    图的割边和割点

    poj3352(外加3117)

    最小割模型

    poj3308, poj3155(偏难)详见:http://www.cnblogs.com/vongang/archive/2012/10/25/2740042.html

    KM算法(最大权/最小权)

    poj2195, poj2400, poj3686

      

    差分约束系统的建立和求解

    表示差分约束以前没接触过,看了看算导。貌似就是一个建模,剩下的就是spfa,或则Bellman-Ford,学习了一下stack实现的spfa,据说这个效率高些;

    poj 1201

    建图:可以得到约束条件 num[b+1]  - num[a] >= c;还有一个条件,0 <= num[i+1] - num[i] <= 1;还是理解的不深,多做练习!

     poj 2983

    题意:一个点集,给出M描述。P, A, B, C,表示a, b的距离确定为c,V,A,B表示a, b的距离至少为1。求能否同时满足这M个描述。

    思路:

    建图:b - a = c  ----> (b - a >= c) && (b - a <= c)  ----> (a - b <= -c) && (b - a <= c);

         b - a >= 1 ----> (a - b <= -1)

    加一个超级源点。题目要求同时满足M个描述,表示这个图是正常图(不含负权回路)。用spfa解,判断是否有负权回路就可以。

    手写的queue,注意别开太小了。RE了两次!

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    
    using namespace std;
    
    const int M = 1024;
    const int inf = ~0u>>1;
    
    struct node {
        int to;
        int val;
        int next;
    } g[2000007];
    
    int head[M], t, n;
    int q[10000000];
    int dis[M];
    int cnt[M];
    bool vis[M];
    
    void init() {
        for(int i = 0; i < M; ++i) {
            head[i] = 0; dis[i] = inf;
            cnt[i] = 0; vis[i] = false;
        }
        t = 1;
    }
    
    void add(int u, int v, int c) {
        g[t].to = v; g[t].val = c;
        g[t].next = head[u]; head[u] = t++;
    }
    
    bool spfa() {
        int i, u, v, val, f = 0, r = 0;
        q[r++] = 0; vis[0] = true;
        dis[0] = 0; //cnt[0] = 1;
        while(f < r) {
            u = q[f++];
            vis[u] = false;
            for(i = head[u]; i; i = g[i].next) {
                v = g[i].to; val = g[i].val;
                if(dis[v] > dis[u] + val) {
                    dis[v] = dis[u] + val;
                    if(!vis[v]) {
                        vis[v] = true;
                        q[r++] = v;
                        if(++cnt[v] > n)   return false;
                    }
                }
            }
        }
        return true;
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int m, i, a, b, c;
        char s[2];
        while(~scanf("%d%d", &n, &m)) {
            init();
            while(m--) {
                scanf("%s", s);
                if(s[0] == 'P') {
                    scanf("%d%d%d", &a, &b, &c);
                    add(b, a, c);
                    add(a, b, -c);
                } else if(s[0] == 'V') {
                    scanf("%d%d", &a, &b);
                    add(a, b, -1);
                }
            }
            for(i = 1; i <= n; ++i) {
                add(0, i, 0);
            }
            if(spfa())  puts("Reliable");
            else    puts("Unreliable");
        }
        return 0;
    }

     

    poj 3159

    题意:flymouse是班长,老师给了一些糖果让他分配。小孩们爱攀比,相互比较糖果数,A小孩认为B小孩的糖果数不能比他的多c个。此外,还有一个小孩snoopy专门监督班长flymouse,现在flymouse想整他,使他所得到的糖果数比snoopy的糖果数尽量多(在让每一个小孩都满意的情况下。)求这个最大数。

    思路:dis[A]表示A小孩的糖果数,dis[B]表示B小孩的糖果数,满足dis[B] - dis[A] <= c  即 dis[B] <= dis[A] + c。也就是说当dis[B] > dis[A] + c时,进行松弛。这样就可以在AB上建一条权值为c的边,求dis[snoopy]到dis[flymouse]的最短路。spfa();

    ps:我发现受1201的影响,2983和3159这两道题的思路都被局限住了。。。应该是我理解的不够T_T

     

    poj 1275


    黑书上的原题,首先将数据整理成:

      r[1...24]---每个小时需要的出纳员的数目;

      num[1...24]---每个小时应征的申请者的数目;

    求s[1...24]----s[i]表示0~i时刻雇佣的出纳员的总数。s[i] - s[i-1]就是在i时刻录用的出纳员的数目。设s[0] = 0,sum为雇佣的所有出纳员的数目。于是可行的方案满足:

      s[j] - s[j - 1] >= 0;

           s[i-1] - s[i] >= -t[i];

           s[24] - s[0] >= sum;

      i = (j + 8 - 1)%24 + 1;

           s[i] - s[j] >= r[i] (i > j);

           s[i] - s[j] >= r[i] - sum (i > j);

     当sum已知的时候,根据这些不等式构图:以0,1,...,24为顶点,如果存在不等式s[a] - s[b] >= c则add(b, a, c)。以0为定点用spfa求出dis[24],如果dis[24] = sum则这是一个可行解。

    ps:莫名奇妙的debug了两个小时,样例就是不过,后来重新敲了一遍过了。。。无语!

     

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <queue>
    
    using namespace std;
    
    const int N = 1024;
    const int M = 30;
    const int inf = ~0u>>2;
    
    struct node {
        int to;
        int val;
        int next;
    } g[N];
    
    int head[M], t;
    int num[M], r[M];
    int dis[M];
    int cnt[M];
    bool vis[M];
    
    void init() {
        for(int i = 0; i < M; ++i) {
            head[i] = 0; vis[i] = false;
            dis[i] = -inf;
        }
        t = 1;
    }
    
    void add(int u, int v, int w) {
        g[t].to = v; g[t].val = w;
        g[t].next = head[u]; head[u] = t++;
    }
    
    int spfa() {
        int i, u, v, w;
        queue<int> q;
        for(i = 0; i < M; ++i)  cnt[i] = 0;
        q.push(0); dis[0] = 0; vis[0] = true; cnt[0] ++;
    
        while(!q.empty()) {
            u = q.front(); q.pop();
            vis[u] = false;
            for(i = head[u]; i; i = g[i].next) {
                v = g[i].to; w = g[i].val;
                if(dis[v] < dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if(!vis[v]) {
                        vis[v] = true;
                        q.push(v);
                        if(++cnt[v] > 24)   return -1;
                    }
                }
            }
        }
        return dis[24];
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int cas, n, i, j;
        int x, sum;
        scanf("%d", &cas);
        while(cas--) {
            memset(num, 0, sizeof(num));
            for(i = 1; i <= 24; ++i) {
                scanf("%d", &r[i]);
            }
            scanf("%d", &n);
            for(i = 0; i < n; ++i) {
                scanf("%d", &x);
                num[x+1]++;
            }
            bool flag = false;
            for(sum = 0; sum <= n; ++sum) {
                init();
                for(i = 1; i <= 24; ++i) {
                    add(i - 1, i, 0);
                    add(i, i - 1, -num[i]);
                }
                add(0, 24, sum);
                for(j = 1; j <= 24; ++j) {
                    i = (j + 7)%24 + 1;
                    if(i > j)   add(j, i, r[i]);
                    else    add(j, i, r[i] - sum);
                }
                if(spfa() == sum) {
                    printf("%d\n", sum);
                    flag = true; break;
                }
            }
            if(!flag)    puts("No Solution");
        }
        return 0;
    }

     

    poj 1364

    题意:国王有一个傻儿子,只会算一个序列S = {a1, a2, ..., an}里的子序列Si = {aSi, aSi+1, ..., aSi+ni}的和sum,并且可以将sum和给定的数ki进行比较,判断大小(sum > ki or sum < ki)。有时候他的判断是错的,但是不想承认,所以要伪造一个序列S,让这个伪造的序列满足他的所有判断都正确。问这个序列是否存在。

     

    这个题数据给的很巧,都是整数,要不然没办法做了!设sum[i]表示sum(a0,...ai)。所以:

    a b gt c => sum[b + a] - sum[a] + 1 > c;
    a b lt c => sum[b + a] - sum[a] + 1 < c;
    转化为:
    sum[b + a] - sum[a] + 1 >= c + 1;
    sum[b + 1] - sum[a] + 1 <= c - 1;
    再转化成求最长路:
    sum[b + a] - sum[a] + 1 >= c + 1;   => add(b + a + 1, a, c + 1);
    sum[a] - (sum[b + a] + 1) >= -(c - 1);  => add(a, b + a + 1, 1 - c);


    最后spfa判断是否有负权回路(也就是这个图存不存在),感谢wy教主指点。

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    
    using namespace std;
    
    const int N = 110;
    const int inf = ~0u>>2;
    
    struct node {
        int to;
        int val;
        int next;
    } g[N*N*N];
    
    int head[N], t;
    int dis[N];
    int cnt[N];
    bool vis[N];
    int n, m;
    
    void init() {
        for(int i = 0; i < N; ++i) {
            head[i] = 0; vis[i] = false;
            cnt[i] = 0; dis[i] = -inf;
        }
        t = 1;
    }
    
    void add(int u, int v, int w) {
        g[t].to = v; g[t].val = w;
        g[t].next = head[u]; head[u] = t++;
    }
    
    bool spfa() {
        int u, v, w, i;
        queue<int> q;
        q.push(0); dis[0] = 0;
        vis[0] = true;
    
        while(!q.empty()) {
            u = q.front(); q.pop();
            for(i = head[u]; i; i = g[i].next) {
                v = g[i].to; w = g[i].val;
                if(dis[v] < dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if(!vis[v]) {
                        vis[v] = true;
                        q.push(v);
                        if(++cnt[v] > n)    return false;
                    }
                }
            }
            vis[u] = false;
        }
        return true;
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int i, a, b, c;
        char s[10];
        while(scanf("%d", &n), n) {
            scanf("%d", &m);
            init();
            for(i = 0; i < m; ++i) {
                scanf("%d %d %s %d", &a, &b, s, &c);
                b += a;
                if(s[0] == 'g')  add(b + 1, a, c + 1);
                else    add(a, b + 1, 1 - c);
            }
            ++n;
            for(i = 1; i <= n; ++i) {
                add(0, i, 0);
            }
            if(spfa())  puts("lamentable kingdom");
            else    puts("successful conspiracy");
        }
        return 0;
    }

     

     

    最小(大)费用最大流 

    poj 2516

    题意:描述的很让人费解,多读几遍就可以了。N个收货地,每个收货要K种物品,每种物品ki(0 <= ki <= 3)个。M个货源地,每个货源地同样K种物品,各种物品ki'(0 <= ki' <= 3)个。然后是K个N*M的矩阵,第ki‘’个矩阵的第i 行第j 列表示从货源地j 到收货地i 运送ki‘’型号的物品的单价。(无比蛋疼的表述,看得我想哭的心都有!!)

    思路:很裸的最小费用最大流,把K种物品分别计算出最小费用最大流,然后求和就可以。

    最小费用最大流详见:

    View Code
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    
    using namespace std;
    
    const int N = 110;
    const int inf = ~0u>>2;
    
    int c[N][N], f[N][N], w[N][N];
    int dis[N];
    int pre[N];
    bool vis[N];
    int s, t;
    
    int ord[N][N];
    int sto[N][N];
    int cos[N][N][N];
    
    void spfa() {
        int u, v;
        queue<int> q;
        q.push(s); dis[s] = 0; vis[s] = true;
    
        while(!q.empty()) {
            u = q.front(); q.pop();
            for(v = 0; v <= t; ++v) {
                if(c[u][v] > f[u][v] && dis[v] > dis[u] + w[u][v]) {
                    dis[v] = dis[u] + w[u][v];
                    pre[v] = u;
                    if(!vis[v]) {
                        vis[v] = true;
                        q.push(v);
                    }
                }
            }
            vis[u] = false;
        }
    }
    
    void mcmf() {
        int i;
        while(1) {
            for(i = 0; i < N; ++i) {
                dis[i] = inf; pre[i] = -1; vis[i] = false;
            }
            spfa();
            if(pre[t] == -1) break;
            int x = t, minf = inf;
            while(pre[x] != -1) {
                minf = min(minf, c[pre[x]][x] - f[pre[x]][x]);
                x = pre[x];
            }
            x = t;
            while(pre[x] != -1) {
                f[pre[x]][x] += minf;
                f[x][pre[x]] = -f[pre[x]][x];
                x = pre[x];
            }
        }
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int n, m, k;
        int i, j, l;
        int ans, flag;
        while(~scanf("%d%d%d", &n, &m, &k)) {
            if(n + m + k <= 0)  break;
    
            memset(ord, 0, sizeof(ord));
            memset(sto, 0, sizeof(sto));
            memset(cos, 0, sizeof(cos));
    
            for(i = 1; i <= n; ++i)
                for(j = 1; j <= k; ++j)
                    scanf("%d", &ord[i][j]);
                    
            for(i = 1; i <= m; ++i) {
                for(j = 1; j <= k; ++j) {
                    scanf("%d", &sto[i][j]);
                }
            }
            for(l = 1; l <= k; ++l) {
                for(i = 1; i <= n; ++i) {
                    for(j = 1; j <= m; ++j) {
                        scanf("%d", &cos[l][i][j]);
                    }
                }
            }
            s = 0; t = m + n + 1;
            ans = 0; flag = 1;
            for(l = 1; l <= k && flag; ++l) {
    
                memset(w, 0, sizeof(w));
                memset(f, 0, sizeof(f));
                memset(c, 0, sizeof(c));
    
                for(i = 1; i <= m; ++i)     c[s][i] = sto[i][l];
                for(i = 1; i <= n; ++i)     c[i + m][t] = ord[i][l];
    
                for(i = 1; i <= m; ++i) {
                    for(j = 1; j <= n; ++j) {
                        c[i][j + m] = sto[i][l];
                    }
                }
    
                for(i = 1; i <= m; ++i) {
                    for(j = 1; j <= n; ++j) {
                        w[i][j + m] = cos[l][j][i];
                        w[j + m][i] = -w[i][j + m];
                    }
                }
                mcmf();
                for(i = 1; i <= n; ++i) {
                    if(c[i + m][t] != f[i + m][t])  {flag = 0; break;}
                }
                for(i = 1; i <= m; ++i) {
                    for(j = 1; j <= n; ++j) {
                        ans += w[i][j + m] * f[i][j + m];
                    }
                }
            }
            if(!flag)    puts("-1");
            else    printf("%d\n", ans);
        }
        return 0;
    }

    poj 2195

    题意:小人往house里走,给出图,m表示小人,H表示house。m每走一步要消费1¥,求让每个小人都进房间的最小消费。

    思路:先求出每一小人到每个house的距离,dis(m, h) = abs(m.x - h.x) + abs(m.y - h.y);然后建图,i -> j(i set(m), j set(H))的容量为1 c(i, j),权值为 dis(i, j)。求出流量f(i, j)

    ans = ∑[f(i, j) * dis(i, j));

    ps: 把图建错了,wa来一下午。。。T_T'

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <queue>
    
    using namespace std;
    
    const int N = 210;
    const int inf = ~0u>>2;
    
    struct node {
        int x;
        int y;
    } stk[N*N];
    
    int c[N][N], f[N][N], w[N][N];
    int dis[N];
    int pre[N];
    bool vis[N];
    int s, t;
    int n, m;
    
    char mp[N][N];
    
    int ABS(int x) {
        return x < 0 ? -x : x;
    }
    
    void spfa() {
        int u, v;
        queue<int> q;
        q.push(s); dis[s] = 0; vis[s] = true;
    
        while(!q.empty()) {
            u = q.front(); q.pop();
            for(v = 0; v <= t; ++v) {
                if(c[u][v] > f[u][v] && dis[v] > dis[u] + w[u][v]) {
                    dis[v] = dis[u] + w[u][v];
                    pre[v] = u;
                    if(!vis[v]) {
                        vis[v] = true;
                        q.push(v);
                    }
                }
            }
            vis[u] = false;
        }
    }
    
    void mcmf() {
        int i, x, minf;
        while(1) {
            for(i = 0; i < N; ++i) {
                dis[i] = inf; vis[i] = false; pre[i] = -1;
            }
            spfa();
            if(pre[t] == -1)    break;
    
            minf = inf; x = t;
            while(pre[x] != -1) {
                minf = min(minf, c[pre[x]][x] - f[pre[x]][x]);
                x = pre[x];
            }
            x = t;
            while(pre[x] != -1) {
                f[pre[x]][x] += minf;
                f[x][pre[x]] = -f[pre[x]][x];
                x = pre[x];
            }
        }
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int i, j, num, hn, mn;
        int ans;
        while(scanf("%d%d", &n, &m), n||m) {
            num = 1;
            memset(mp, 0, sizeof(mp));
            for(i = 0; i < n; ++i) {
                scanf("%s", mp[i]);
                for(j = 0; j < m; ++j) {
                    if(mp[i][j] == 'm') {
                        stk[num].x = i; stk[num++].y = j;
                    }
                }
            }
            mn = num - 1;
            for(i = 0; i < n; ++i) {
                for(j = 0; j < m; ++j) {
                    if(mp[i][j] == 'H') {
                        stk[num].x = i; stk[num++].y = j;
                    }
                }
            }
            hn = num - 1;
            memset(w, 0, sizeof(w));
            memset(c, 0, sizeof(c));
            for(i = 1; i <= mn; ++i) {
                for(j = mn + 1; j <= hn; ++j) {
                    w[i][j] = ABS(stk[i].x - stk[j].x) + ABS(stk[i].y - stk[j].y);
                    c[i][j] = 1; w[j][i] = -w[i][j];
                }
            }
            s = 0; t = hn + 1;
            for(i = 1; i <= mn; ++i) {
                c[0][i] = 1;
            }
            for(i = mn + 1; i <= hn; ++i) {
                c[i][t] = 1;
            }
    
            memset(f, 0, sizeof(f));
            mcmf();
            ans = 0;
            for(i = 1; i <= mn; ++i) {
                for(j = mn + 1; j <= hn; ++j) {
                    ans += w[i][j]*f[i][j];
                }
            }
            printf("%d\n", ans);
        }
        return 0;
    }

     poj 3422

    题意:一个n*n的棋盘,每个格子里有一个非负整数,现在要从(1,1)点到(n,n)点走k次,每以小步只能向下或者向右,累加每小步走过的格子里边的数,然后将它置0。求最大累加值。

    思路:最大费用最大流,据说是个经典问题,在群里问了一下,都建议我百度。好吧,以下内容是google的。

    建图:把每个点拆成两个,A = (i - 1)*n + j,A‘ = A + n*n。

    1、A->A'建一条容量为1,费用为map[i][j]的边,再建一条容量为inf,费用为0的边(这里是因为如果A点被一次增广路覆盖以后费用变成0,并且保证能无限次的再经过A点找增广路)。

    2、与A相邻的点B(右或者下)。A‘ -> B建一条容量为inf,费用为0的边(同上,保证A’->B连通)。

    3、最后从源点S到1建一条容量为k,费用为0的边,从2*n*n到汇点T建一条容量为k,费用为0的边。

     然后spfa求增广路上的最长路,然后累加就可以。

    ps:写邻接表一定memset(head, -1, sizeof(head)); 从0开始存,就因为这个昨晚TLE到1点半。。。想死的心都有!

    View Code
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    
    using namespace std;
    
    const int N = 30000;
    const int M = 55;
    const int inf = ~0u>>2;
    
    struct node {
        int u, v;
        int cap;
        int cost;
        int next;
    } g[1000000];
    
    int q[1000000];
    int head[N], t;
    int dis[N];
    int pre[N];
    bool vis[N];
    
    int mp[M][M];
    int S, T;
    
    void add(int u, int v, int c, int f) {
        g[t].u = u; g[t].v = v; g[t].cap = c; g[t].cost = f; g[t].next = head[u]; head[u] = t++;
        g[t].u = v; g[t].v = u; g[t].cap = 0; g[t].cost = -f; g[t].next = head[v]; head[v] = t++;
    }
    
    bool spfa() {
        int u, v, i, f = 0, r = 0;
        for(i = S; i <= T; ++i) {
            dis[i] = -inf; vis[i] = false; pre[i] = -1;
        }
        q[r++] = S; vis[S] = true; dis[S] = 0;
        while(f < r) {
            u = q[f++]; vis[u] = false;
            for(i = head[u]; i != -1; i = g[i].next) {
                v = g[i].v;
                if(g[i].cap > 0 && dis[v] < dis[u] + g[i].cost) {
                    dis[v] = dis[u] + g[i].cost;
                    pre[v] = i;
                    if(!vis[v]) {
                        vis[v] = true;
                        q[r++] = v;
                    }
                }
            }
        }
        if(pre[T] == -1)    return false;
        return true;
    }
    
    int mcmf() {
        int minf, u, ans = 0;
        while(spfa()) {
            u = T; minf = inf;
            while(u != S) {
                minf = min(minf, g[pre[u]].cap);
                u = g[pre[u]].u;
            }
            u = T;
            while(u != S) {
                g[pre[u]].cap -= minf;
                g[pre[u]^1].cap += minf;
                u = g[pre[u]].u;
            }
            ans += dis[T]*minf;
        }
        return ans;
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int i, j, n, k;
        int x, u, v;
        while(~scanf("%d%d", &n, &k)) {
            t = 0; memset(head, -1, sizeof(head));
            S = 0; x = n*n; T = 2*n*n + 1;
            for(i = 1; i <= n; ++i) {
                for(j = 1; j <= n; ++j) {
                    scanf("%d", &mp[i][j]);
                }
            }
            for(i = 1; i <= n; ++i) {
                for(j = 1; j <= n; ++j) {
                    u = (i - 1)*n + j;
                    v = u + x;
                    add(u, v, 1, mp[i][j]);
                    add(u, v, inf, 0);
                    if(i < n)    add(v, u + n, inf, 0);
                    if(j < n)    add(v, u + 1, inf, 0);
                }
            }
            add(S, 1, k, 0);
            add(2*x, T, k, 0);
            printf("%d\n", mcmf());
        }
        return 0;
    }

    双连通分量

     poj 2942

    题意:一群圆桌骑士闲的蛋疼一开会就要打架 ,国王老大看不下去了,找来军师梅林。梅林研究了一下说了两个规则,1、互相瞪眼的两个骑士不能挨着坐。2、一个圆桌上只能有奇数个骑士。

    如果有的骑士因为这两个条件没地方坐,那就直接开除(没办法,人缘不行,^^)。给出哪些骑士互相瞪眼,算最少开除几个。

    思路:建图时按照矛盾关系的补图建图,也就是说不矛盾的两个骑士连一条无向边。因为是无向图,所以先求无向图的点双连通分量

    1、得到的双连通分量,找到一个包含所有点的圈,如果这个圈是奇圈,那么这个双连通分量是合法的。如果这个圈是偶圈,假设在这个双连通分量中存在另外一个子圈是奇圈,那么其他的点一点在另外一个是奇圈的子圈内部。 所以:如果一个双连通分量存在一个奇圈,则这个双连通分量合法

    2、可以用二分图判是否存在奇圈。从二分图的概念知道,二分图一定是个偶圈(从一个节点U出发再回到U经过偶数条边),所以只要判断双连通分量不是二分图就可以确定它含有奇圈。用dfs对图进行01染色,如果存在前驱和后继节点同色,则可以确定不是二分图。

    ps:对于这道题,已经不是做题了,而是啃题。。。

    View Code
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    
    #define REP(i, n)   for(i = 0; i < (n); ++i)
    #define FOR(i, l, h)    for(i = (l); i <= (h); ++i)
    #define FORD(i, h, l)   for(i = (h); i >= (l); --i)
    #define clear(x, i)     memset(x, i, sizeof(x));
    
    
    using namespace std;
    
    
    const int N = 1024;
    
    int ans[N][N];
    int mp[N][N];
    int dfn[N];
    int low[N];
    int col[N];
    int cnt[N];
    int stk[N];
    int is[N];
    
    int top, ind, num, n;
    
    void init() {
        clear(dfn, 0); clear(low, 0);
        clear(ans, 0); clear(cnt, 0);
        clear(stk, 0); clear(is, 0);
        top = ind = num = 0;
    }
    
    bool check(int n, int f, int i, int c) {
        int j;
        REP(j, cnt[n]) {
            if(mp[ans[n][i]][ans[n][j]] && j != f) {
                if(!col[j]) {
                    col[j] = 3 - c;
                    if(check(n, i,  j, col[j])) return 1;
                } else if(col[i] == col[j]) return 1;
            }
        }
        return 0;
    }
    
    void tarjan(int u) {
        stk[top++] = u;
        dfn[u] = low[u] = ++ind;
        int v, i;
        FOR(v, 1, n) {
            if(mp[u][v]) {
                if(!dfn[v]) {
                    tarjan(v);
                    low[u] = min(low[u], low[v]);
                    if(dfn[u] <= low[v]) {
                        ans[num][0] = u; stk[top] = 0;
                        for(i = 1; stk[top] != v; ++i) {
                            ans[num][i] = stk[--top];
                        }
                        if(i > 2)   cnt[num++] = i;
                    }
                } else low[u] = min(low[u], dfn[v]);
            }
        }
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int m, i, j, x, y, res;
        while(scanf("%d%d", &n, &m), n || m) {
            init();
            REP(i, n + 1)
                REP(j, n + 1)
                    mp[i][j] = (i == j ? 0 : 1);
    
            while(m--) {
                scanf("%d%d", &x, &y);
                mp[x][y] = mp[y][x] = 0;
            }
    
            FOR(i, 1, n)
                if(!dfn[i]) tarjan(i);
    
            REP(i, num) {
                clear(col, 0); col[0] = 1;
                if(check(i, -1, 0, 1)) {
                    REP(j, cnt[i])  is[ans[i][j]] = 1;
                }
            }
            res = 0;
            FOR(i, 1, n)    res += (is[i]^1);
            printf("%d\n", res);
        }
        return 0;
    }

     poj 3694

    题意:N个点M条无向边构成一个图,然后是Q个询问,每次加一条边,求每加一条边图中还剩的桥的个数。

    思路:先用tarjan求出桥边,这里如果u->v是桥边的话记录v点就可以表示。在求桥边的过程中记录每个节点的父节点,然后求最近公共祖先。这里有两个处理:1、因为如果a->b加一条边的话,a,b,a和b的最近公共祖先就构成一个环,上边所有的桥边都要剪掉。2、a->b连上,a到b这条路上所有的桥也都删掉,同样b->a。

    详见代码:

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <queue>
    #include <stack>
    #include <map>
    #include <vector>
    #include <algorithm>
    
    #define REP(i, n)   for(i = 0; i < n; ++i)
    #define FOR(i, l, r)    for((i) = (l); (i) <= (r); ++(i))
    #define FORD(i, r, l)   for((i) = (r); (i) >= (l); --(i))
    #define CL(x, i)    memset((x), (i), sizeof((x)))
    
    using namespace std;
    
    const int N = 100010;
    
    struct node {
        int to;
        int next;
    } g[N<<4];
    
    int cnt, t, ind;
    int head[N];
    int dfn[N];
    int low[N];
    int brg[N];
    int far[N];
    bool vis[N];
    
    void init() {
        CL(vis, false);
        CL(dfn, 0);
        CL(low, 0);
        CL(brg, 0);
        CL(far, 0);
        CL(head, -1);
        cnt = ind = t = 0;
    }
    
    void add(int u, int v) {
        g[t].to = v; g[t].next = head[u]; head[u] = t++;
    }
    
    void dfs(int u) {    //tarjan求桥
        vis[u] = true;
        dfn[u] = low[u] = ++ind;
        int i, v;
        for(i = head[u]; i != -1; i = g[i].next) {
            v = g[i].to;
            if(!dfn[v]) {
                far[v] = u;
                dfs(v);
                low[u] = min(low[u], low[v]);
                if(low[v] > dfn[u]) {
                    ++cnt;
                    brg[v] = 1;    //记录桥边
                }
            } else if(v != far[u] && vis[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
    }
    
    void lca(int u, int v) {
        while(dfn[v] > dfn[u]) {    //v到u路径上
            if(brg[v])   {
                --cnt; brg[v] = 0;
            }
            v = far[v];
        }
        while(dfn[u] > dfn[v]) {    //u到v路径上
            if(brg[u]) {
                --cnt; brg[u] = 0;
            }
            u = far[u];
        }
        while(u != v) {    //u,v,u和v的最近公共祖先所构成的环
            if(brg[u]) {
                --cnt; brg[u] = 0;
            }
            if(brg[v]) {
                --cnt; brg[v] = 0;
            }
            v = far[v]; u = far[u];
        }
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int n, m, q, i;
        int cas = 0, a, b;
        while(~scanf("%d%d", &n, &m)) {
            if(n + m == 0)  break;
            init();
            for(i = 0; i < m; ++i) {
                scanf("%d%d", &a, &b);
                add(a, b);
                add(b, a);
            }
            dfs(1);
            scanf("%d", &q);
            printf("Case %d:\n", ++cas);
            while(q--) {
                scanf("%d%d", &a, &b);
                lca(a, b);
                printf("%d\n", cnt);
            }
            cout << endl;
        }
        return 0;
    }

    强连通分支及其缩点 

    poj 2186

    题意:如果AB有一条边,则表示A认为B是popular,如果AB,BC,则A同样认为C是popular。求被所有的点都认为是popular的点的个数。

    思路:可以先缩点,得到一个新图,再按原图的边把个个缩点连接,找出度为0的缩点。此缩点的包含的所有点都是所求的点。

    View Code
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    
    #define REP(i, n)   for(i = 0; i < n; ++i)
    #define FOR(i, l, h)    for(i = l; i <= h; ++i)
    #define FORD(i, h, l)   for(i = h; i >= l; --i)
    #define CL(x, n)    memset(x, n, sizeof(x))
    
    using namespace std;
    
    const int N = 10007;
    const int M = 50007;
    
    struct node {
        int to;
        int next;
    } g[M<<1];
    
    int head[N];
    int dfn[N];
    int low[N];
    int scc[N];
    int val[N];
    int stk[N];
    int out[N];
    bool vis[N];
    int cnt, ind, top, t;
    
    void init() {
        CL(head, -1);
        CL(dfn, 0); CL(low, 0);
        CL(scc, 0); CL(val, 0);
        CL(stk, 0); CL(vis, 0);
        CL(out, 0);
        cnt = ind = top = t = 0;
    }
    
    void add(int u, int v) {
        g[t].to = v; g[t].next = head[u]; head[u] = t++;
    }
    
    void tarjan(int u) {
        vis[u] = true;
        dfn[u] = low[u] = ++ind;
        stk[top++] = u;
        int i, v;
        for(i = head[u]; i != -1; i = g[i].next) {
            v = g[i].to;
            if(!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if(vis[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if(low[u] == dfn[u]) {
            cnt ++;
            do {
                v = stk[--top];
                scc[v] = cnt;
                val[cnt]++;
            } while(v != u);
        }
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int n, m, a, b;
        int i, j, ou, f;
        while(~scanf("%d%d", &n, &m)) {
            init();
            while(m--) {
                scanf("%d%d", &a, &b);
                add(a, b);
            }
            FOR(i, 1, n) {
                if(!dfn[i]) tarjan(i);
            }
            FOR(j, 1, n) {
                for(i = head[j]; i != -1; i = g[i].next) {
                    a = g[i].to;
                    if(scc[j] != scc[a]) {
                        out[scc[j]]++;
                    }
                }
            }
            ou = 0;
            FOR(i, 1, cnt) {
                if(!out[i]) {ou++;  f = i;}
            }
            if(ou > 1)  printf("0\n");
            else    printf("%d\n", val[f]);
        }
        return 0;
    }

     poj 3529

    题意:N*M的棋盘,每个格子里有一定数目的ores,从左上角开始走,每次只能向右或向下,每走过一个格子里边的矿石被取干净。途中可能遇到传送阵,每一传送阵有一个值(u, v)。表示可以把你传送到(u, v)处,当然你可以选择传送还是不传送。求最大可能得到的矿石数。

    思路:很明显要tarjan缩点,然后重新建图。开始想的是建成的图必定是一颗树,所以用树形dp的思想dfs,可是wa掉N次后本菜打消了这个念头。后来看到AC大神blog里写过,用spfa求最长路。这也没什么难的嘛,最近一直在跟spfa打交道,三角形不等式快用烂了。。。可是。。。可是!!尼嘛从昨晚wa了整整一晚上,今天早晨起来一看,狗血的发现建图的时候把权值加错了。。。我都让我整整刷了一页的wa。。。今天早晨又拍了一遍,妹的又是因为一个很小的错误调了N久。。。做题不仔细,这就是后果。。。T_T

    要注意的几点

    1、in the order from north to south then west to east,这句坑死人不偿命的话。。

    2、传送阵也可以下右走或者向下走。

    3、重新建图时加的是缩点的权。。。在这里wa死了!T_T

    View Code
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    
    #define REP(i, n)   for(i = 0; i < n; ++i)
    #define FOR(i, l, h)    for(i = l; i <= h; ++i)
    #define FORD(i, h, l)   for(i = h; i >= l; --i)
    #define CL(x, i)    memset(x, i, sizeof(x))
    
    using namespace std;
    
    const int N = 2048;
    const int inf = ~0u>>2;
    
    struct node {
        int to;
        int next;
    } g[N*100];
    
    struct node2 {
        int to;
        int val;
        int next;
    } G[N*100];
    
    int head[N], t;
    int head2[N], t2;
    
    int dfn[N], low[N], stk[N*100];
    int val[N], ore[N], scc[N];
    int top, ind, cnt;
    bool inq[N];
    
    int dis[N];
    bool vis[N];
    
    char mp[50][50];
    int num[50][50];
    
    void init() {
        CL(head, -1); CL(head2, -1);
        CL(vis, false); CL(inq, false);
        CL(dfn, 0); CL(low, 0);
        CL(stk, 0); CL(val, 0);
        CL(ore, 0); CL(num, 0);
        CL(mp, 0); CL(scc, 0);
    
        t = t2 = ind = top = cnt = 0;
    }
    
    void add(int u, int v) {
        g[t].to = v; g[t].next = head[u]; head[u] = t++;
    }
    
    void add2(int u, int v, int w) {
        G[t2].to = v; G[t2].val = w; G[t2].next = head2[u]; head2[u] = t2++;
    }
    
    void tarjan(int u) {
        inq[u] = true;
        dfn[u] = low[u] = ++ind;
        stk[top++] = u;
        int i, v;
        for(i = head[u]; i != -1; i = g[i].next) {
            v = g[i].to;
            if(!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if(inq[v]) {
                low[u] = min(low[u], dfn[v]);
            }
        }
        if(low[u] == dfn[u]) {
            cnt ++;
            do {
                v = stk[--top];
                scc[v] = cnt;
                inq[v] = false;
                val[cnt] += ore[v];
            } while(u != v);
        }
    }
    
    int spfa() {
        int i, u, v, w;
        queue<int> q;
        REP(i, N)   dis[i] = -inf;
        q.push(0); vis[0] = true; dis[0] = 0;
    
        while(!q.empty()) {
            u = q.front(); q.pop(); vis[u] = false;
            for(i = head2[u]; i != -1; i = G[i].next) {
                v = G[i].to; w = G[i].val;
                if(dis[v] < dis[u] + w) {
                    dis[v] = dis[u] + w;
                    if(!vis[v]) {
                        vis[v] = true;
                        q.push(v);
                    }
                }
            }
        }
        int ans = 0;
        for(i = 0; i <= cnt; ++i) {
            ans = max(ans, dis[i]);
        }
        return ans;
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int t, n, m, i, j;
        int cct, u, v;
        scanf("%d", &t);
        while(t--) {
            init();
            scanf("%d%d", &n, &m);
            cct = 0;
            REP(i, n) {
                scanf("%s", mp[i]);
                REP(j, m) {
                    if(mp[i][j] == '#') continue;
                    num[i][j] = ++cct;
                    if(mp[i][j] != '*') ore[cct] = mp[i][j] - '0';
                }
            }
            REP(i, n) {
                REP(j, m) {   //点1
                    if(mp[i][j] == '#') continue;
                    if(mp[i][j] == '*') {
                        scanf("%d%d", &u, &v);
                        if(mp[u][v] != '#')     add(num[i][j], num[u][v]);
                    }
                    if(i + 1 < n && mp[i+1][j] != '#')  add(num[i][j], num[i + 1][j]);  //点2
                    if(j + 1 < m && mp[i][j+1] != '#')  add(num[i][j], num[i][j + 1]);
                }
            }
            FOR(i, 1, cct) {
                if(!dfn[i]) tarjan(i);
            }
            add2(0, scc[1], val[scc[1]]);   //点3,val[scc[1]]!
            FOR(u, 1, cct) {
                for(i = head[u]; i != -1; i = g[i].next) {
                    v = g[i].to;
                    if(scc[u] != scc[v]) {
                        add2(scc[u], scc[v], val[scc[v]]);
                    }
                }
            }
            printf("%d\n", spfa());
        }
        return 0;
    }

    poj 3114

    题意: 给你一堆城市以及这些城市之间传递消息需要的时间(城市2到城市1的所需要的时间不一定等于城市1到城市2所需要的时间),如果两个城市之间相互可以给对方传递消息,则认为这两个城市是一个国家的,则,这两个城市可以用另一种方法传递消息,花费时间为0.现在问你很多次某两个城市之间传递消息所花的最少时间是多少。

    思路:题意没看懂,直接搜的翻译。知道题意这题基本就解决了。。。在一个强连通分量里的点表示同一个国家的,然后重新加图,spfa求最短路就可以。数组开小了,RE 1次。。。

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    
    #define REP(i, n)       for(i = 0; i < n; ++i)
    #define FOR(i, L, H)    for(i = L; i <= H; ++i)
    #define FORD(i, H, L)   for(i = H; i >= L; --i)
    #define CL(arr, val)    memset(arr, val, sizeof(arr))
    
    using namespace std;
    
    const int N = 550;
    const int inf = ~0u>>2;
    
    struct node {
        int to;
        int val;
        int next;
    } g[N*N];
    
    int G[N][N];
    
    int ind, cnt, top;
    int head[N], t;
    int dfn[N], low[N];
    int dis[N], scc[N];
    int stk[N*N];
    
    bool inq[N], vis[N];
    int n, m;
    
    void init() {
        CL(head, -1); CL(G, 0);
        CL(dfn, 0); CL(low, 0);
        CL(scc, 0); CL(stk, 0);
        CL(vis, 0);
        t = ind = cnt = top = 0;
    }
    
    void add(int u, int v, int w) {
        g[t].to = v; g[t].val = w; g[t].next = head[u]; head[u] = t++;
    }
    
    void tarjan(int u) {
        vis[u] = true;
        dfn[u] = low[u] = ++ind;
        stk[top++] = u;
        int i, v;
    
        for(i = head[u]; i != -1; i = g[i].next) {
            v = g[i].to;
            if(!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if(vis[v])   low[u] = min(low[u], dfn[v]);
        }
        if(low[u] == dfn[u]) {
            ++cnt;
            do {
                v = stk[--top];
                scc[v] = cnt;
                vis[v] = false;
            } while(v != u);
        }
    }
    
    int spfa(int s, int t) {
        if(s == t)  return 0;
        queue<int> q;
        int v, u;
        REP(v, N)   dis[v] = inf, inq[v] = false;
        q.push(s); dis[s] = 0; inq[s] = true;
    
        while(!q.empty()) {
            u = q.front(); q.pop(); inq[u] = false;
    
            for(v = 1; v <= cnt; ++v) {
                if(G[u][v]) {
                    if(dis[v] > dis[u] + G[u][v]) {
                        dis[v] = dis[u] + G[u][v];
                        if(!inq[v]) {
                            inq[v] = true;
                            q.push(v);
                        }
                    }
                }
            }
        }
        return dis[t];
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int q, i, ans;
        int u, v, w;
        while(scanf("%d%d", &n, &m), n) {
            init();
            while(m--) {
                scanf("%d%d%d", &u, &v, &w);
                add(u, v, w);
            }
            FOR(i, 1, n)   if(!dfn[i])  tarjan(i);
            FOR(u, 1, n) {
                for(i = head[u]; i != -1; i = g[i].next) {
                    v = g[i].to; w = g[i].val;
                    if((scc[u] != scc[v] && G[scc[u]][scc[v]] == 0) || G[scc[u]][scc[v]] > w) {
                         G[scc[u]][scc[v]] = w;
                    }
                }
            }
            scanf("%d\n", &q);
            while(q--) {
                scanf("%d%d", &u, &v);
                ans = spfa(scc[u], scc[v]);
                if(ans == inf)  puts("Nao e possivel entregar a carta");
                else    printf("%d\n", ans);
            }
            cout << endl;
        }
        return 0;
    }

    图的割边和割点

    poj 3352&&3117

    题意  :给一个无向图,判断加多少无向边能构成无向双连通图;

    关于无向图双连通问题

    POJ 3177&& 3352

    最小割模型

    poj 3308

    题意:一个棋盘,每行每列可以建一杆机关枪,每杆枪都可以打到棋盘尽头。当然,每个地方的造价不同。现在有一些伞兵落在棋盘上,问建一定数目的机关枪把所有的伞兵都消灭,并且保证造这些机关枪的费用的乘积最小。

    思路:这里让求乘积最小,已知log(a*b)  = loga + logb。所以可以把造假转成log(val)。求和最小,也就是求最小割->最大流。

    建图:已知棋盘为n*m。S与i (1 <= i <= n)相连,容量为log(val[i])。j ( n < j <= n + m) 与T相连,容量为log(val[j])。设伞兵降落的位置为(u, v),则u -> v + n相连,容量为inf。这样保证最小割在S->i,或则j -> T的边集上取到,而不会在i -> j上。

    研究了一下Dinic的邻接表写法,表示很网上的代码很纠结。。。很乱

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    
    #define REP(i, n)   for(i = 0; i < n; ++i)
    #define FOR(i, l, h)    for(i = l; i <= h; ++i)
    #define FORD(i, h, l)   for(i = h; i >= l; --i)
    #define CL(arr, val)    memset(arr, val, sizeof(arr))
    
    using namespace std;
    
    const int N = 3000;
    const double inf = ~0u;
    
    struct node {
        int to;
        double val;
        int next;
    } g[N*N];
    
    int head[N], t;
    int layer[N];
    int q[N*N];
    
    int S, T;
    
    void init() {
        CL(head, -1);
        t = 0;
    }
    
    void add(int u, int v, double c) {
        g[t].to = v; g[t].val = c; g[t].next = head[u]; head[u] = t++;
        g[t].to = u; g[t].val = 0; g[t].next = head[v]; head[v] = t++;
    }
    
    bool bfs() {
        CL(layer, -1);
        int u, v, f, r, i;
        f = r = 0;
        q[r++] = S; layer[S] = 1;
        while(f < r) {
            u = q[f++];
            for(i = head[u]; i != -1; i = g[i].next) {
                v = g[i].to;
                if(layer[v] == -1 && g[i].val > 0) {
                    layer[v] = layer[u] + 1;
                    if(v == T)  return true;
                    q[r++] = v;
                }
            }
        }
        return false;
    }
    
    double dfs() {
        int u, i, e, top, pos;
        double flow, sum = 0;
        top = 1;
        while(top) {
            u = (top == 1) ? S : g[q[top-1]].to;
            if(u == T) {
                flow = inf;
                FOR(i, 1, top - 1) {
                    e = q[i];
                    if(g[e].val < flow) {
                        flow = g[e].val; pos = i;
                    }
                }
                FOR(i, 1, top - 1) {
                    e = q[i];
                    g[e].val -= flow;
                    g[e^1].val += flow;
                }
                top = pos;
                sum += flow;
            } else {
                for(i = head[u]; i != -1; i = g[i].next) {
                    if(g[i].val > 0 && layer[g[i].to] == layer[u] + 1)  break;
                }
                if(i != -1)     q[top++] = i;
                else {
                    --top; layer[u] = -1;
                }
            }
        }
        return sum;
    }
    
    double Dinic() {
        double res = 0;
        while(bfs())    res += dfs();
        return res;
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int t, n, m, l, i, u, v;
        double val;
        scanf("%d", &t);
        while(t--) {
            init();
            scanf("%d%d%d", &n, &m, &l);
            S = 0; T = n + m + 1;
            FOR(i, 1, n) {
                scanf("%lf", &val);
                add(S, i, log(val));
            }
            FOR(i, 1, m) {
                scanf("%lf", &val);
                add(i + n, T, log(val));
            }
            while(l--) {
                scanf("%d%d", &u, &v);
                add(u, v + n, inf);
            }
            printf("%.4lf\n", exp(Dinic()));
        }
        return 0;
    }

     KM算法

    poj 2195

    前边做过,建图求最小费用最大流。。。当时没看KM算法,原来还可以当成二分图来做,求最小权值匹配。建图还是那样建,给H编号,然后接着原来的号给m编号。然后用邻接矩阵建无向图,网上说的KM一般都是在求二分图最大权值匹配,可以把边的权值取反,求最大匹配。

    当模板了。。

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    
    #define REP(i, n)   for(i = 0; i < n; ++i)
    #define FOR(i, l, h)    for(i = l; i <= h; ++i)
    #define FORD(i, h, l)   for(i = h; i >= l; --i)
    #define CL(arr, val)    memset(arr, val, sizeof(arr))
    
    using namespace std;
    
    const int N = 1024;
    const int inf = ~0u>>2;
    
    int x[N], y[N];
    int lx[N];
    int ly[N];
    int mp[N][N];
    int num[N][N];
    char c[N][N];
    int linky[N];
    int slack[N];
    bool visx[N];
    bool visy[N];
    int n, cnt;
    
    int Abs(int x) {
        return x < 0 ? -x : x;
    }
    
    bool dfs(int x) {    //Hungry算法的思想求完全匹配
        int y, d;
        visx[x] = true;
        REP(y, cnt) {
            d = lx[x] + ly[y] - mp[x][y];
            if(d == 0 && !visy[y]) {
                visy[y] = true;
                if(linky[y] == -1 || dfs(linky[y])) {
                    linky[y] = x;
                    return true;
                }
            } else slack[y] = min(slack[y], d);
        }
        return false;
    }
    
    void KM() {
        int i, j, k, ans = 0, d;
        REP(i, cnt) {
            ly[i] = 0; linky[i] = -1; lx[i] = -inf;
            REP(j, cnt) {
                lx[i] = max(lx[i], mp[i][j]);
            }
        }
        REP(k, cnt) {    //对每一个点找一次完全匹配,知道整个图实现完全匹配
            CL(visx, false);
            CL(visy, false);
            REP(i, cnt)    slack[i] = inf;
            while(!dfs(k)) {
                d = inf;
                REP(i, cnt) if(!visy[i]) d = min(d, slack[i]);
                REP(i, cnt) {
                    if(visx[i]) {visx[i] = false; lx[i] -= d;}
                    if(visy[i]) {visy[i] = false; ly[i] += d;}
                }
            }
        }
        REP(i, cnt)    ans += lx[i] + ly[i];
        ans /= -2;
        cout << ans << endl;
    }
    
    void build(int n, int m) {
        int i, j;
        cnt = 0;
        CL(c, 0);
        REP(i, n) {
            scanf("%s", c[i]);
            REP(j, m) {
                if(c[i][j] == 'H') {
                    x[cnt] = i;
                    y[cnt++] = j;
                }
            }
        }
        int l = cnt;
        REP(i, n) {
            REP(j, m) {
                if(c[i][j] == 'm') {
                    x[cnt] = i;
                    y[cnt++] = j;
                }
            }
        }
        REP(i, cnt) REP(j, cnt) mp[i][j] = -inf;
        REP(i, l) {
            FOR(j, l, cnt - 1) {
                mp[j][i] = mp[i][j] = -(Abs(x[i] - x[j]) + Abs(y[i] - y[j]));
            }
        }
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int n, m;
        while(~scanf("%d%d", &n, &m)) {
            if(n + m == 0)  break;
            build(n, m);
            KM();
        }
        return 0;
    }

     

    poj 3686

    题意:N个订单,M个机器,给出第i个订单在第j个机器完成所用的时间cost[i][j],求总耗时的平均值最小。

    思路:拆点拆的很巧,比如我们一直一组数据:

    N个订单,每个完成的时间为T0,T1, T2, T3...Tn-1;

    所以总时间为TIME = T0*N + T1*(n-1) + T2*(N-2) + ... + Tn-1;

    因为前边的单独完成一个订单所耗的时间会推迟后边的时间。

    T0

    T0 + T1

    T0 + T1 + T2

    ....

    T0 + T1 + T2 + .. + Tn-1

    理解上边的公式拆点就简单了。设第i个订单,把第j个机器拆成N个点,这N个点中的第k个点表示第i个订单在j机器上倒数第k个顺序完成。

    所有的点共 N*M个。

    ps:注意KM算法O(n^3)时要dfs的点是N个,不是N*M个,因为X集合里只会有N个元素,TLE两次。。。

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    
    #define CL(arr, val)    memset(arr, val, sizeof(arr))
    #define REP(i, n)       for(i = 0; i < n; ++i)
    #define FOR(i, l, h)    for(i = l; i <= h; ++i)
    #define FORD(i, h, l)   for(i = h; i >= l; --i)
    
    using namespace std;
    
    const int N = 55;
    const int M = N*(N+1);
    const int inf = ~0u>>2;
    
    int mp[N][N*N];
    int cost[N][N];
    int linky[M];
    int lx[M], ly[M];
    int slack[M];
    bool vx[M];
    bool vy[M];
    
    int cnt, n;
    
    bool dfs(int t) {
        int u, d;
        vx[t] = true;
        REP(u, cnt) {
            d = lx[t] + ly[u] - mp[t][u];
            if(d == 0 && !vy[u]) {
                vy[u] = true;
                if(linky[u] == -1 || dfs(linky[u])) {
                    linky[u] = t;
                    return true;
                }
            } else if(d < slack[u]) slack[u] = d;
        }
        return false;
    }
    
    int KM() {
        int i, j, k, d, ans = 0;
        REP(i, cnt) {
           lx[i] = 0; ly[i] = 0; linky[i] = -1;
        }
        REP(i, n) {
            lx[i] = -inf;
            REP(j, cnt)  lx[i] = max(lx[i], mp[i][j]);
        }
        REP(k, n) {
            CL(vx, false);
            CL(vy, false);
            REP(i, cnt)  slack[i] = inf;
            while(!dfs(k)) {
                d = inf;
                REP(i, cnt) if(!vy[i])   d = min(d, slack[i]);
                REP(i, cnt) {
                    if(vy[i])   {vy[i] = false; ly[i] += d;}
                    if(vx[i])   {vx[i] = true; lx[i] -= d;}
                }
            }
        }
        REP(i, cnt)  ans += lx[i] + ly[i];
        return ans;
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int t, m, i, j, k;
        double ans;
        scanf("%d", &t);
        while(t--) {
            scanf("%d%d", &n, &m);
            REP(i, n)   REP(j, m)   scanf("%d", &cost[i][j]);
            REP(i, n) {
                REP(j, m) {
                    REP(k, n) {
                        mp[i][j*n+k] = -(k+1)*cost[i][j];
                    }
                }
            }
            cnt = n*m;
            ans = -1.0*KM()/n;
            printf("%.6f\n", ans);
        }
        return 0;
    }

     

     

     

    ps: 图论这一节到这里基本刷完了,整整两周时间,又把训练计划托慢了。。。最大的感觉就是图论太广了。广一个最小割模型就有很多东西,要覆盖的东西还很多。。。跳过了两道题,3155是分数规划,在胡伯涛的《最小割模型在信息学竞赛中的应用》上讲到了,不过本菜表示还是不会。。。过段时间再看吧,貌似做图论到了一个高原期。下周搞其它的,图论这个东西还会再搞的!

     

     

     

     

     

     

     

  • 相关阅读:
    C# Large Files MD5 C# 获取大文件MD5
    C# SmtpClient 发邮件
    Windows Server 2008 R2 install Visual Studio 2015 failed
    C# 下载泛型数据
    SetApartmentState(ApartmentState state).Ensure that your Main function has STAThreadAttribute marked on it. This exception is only raised if a debugger is attached to the process
    ADO.NET
    WPF DataGrid row background converter datagrid 行背景随绑定数据变化,转换器
    C# 截图ScreenCapture,保存
    打印发现function toUpperCase() { [native code] }
    正则匹配test
  • 原文地址:https://www.cnblogs.com/vongang/p/2447566.html
Copyright © 2020-2023  润新知