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是分数规划,在胡伯涛的《最小割模型在信息学竞赛中的应用》上讲到了,不过本菜表示还是不会。。。过段时间再看吧,貌似做图论到了一个高原期。下周搞其它的,图论这个东西还会再搞的!

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/vongang/p/2447566.html