国家集训队 部落战争 网络流最小路径覆盖 洛谷P2172

洛谷AC传送门!

step1:

题目大意

有一张M x N的网格图,有一些点为“ * ”可以走,有一些点为“ x ”不能走,每走一步你都可以移动R * C 个格子(参考象棋中马的走法),且不能回头,已经走过的点不能再被走第二次。

每次,你可以从任意“ * ”能走的点出发,求至少要多少次才能走完所有的能走的点。

step2:

题意翻译

​ 那么题意便可以转化为: 一条路径可以看做从任意一个没有到达过的可通过的点出发到任意一个其他的可以通过却没有被到达过的点的一条路径, 要使每个点都被经过, 并且每个点都只能被经过一次。

实现方法:

乍一看,这不就是网络流/二分图中的最小路径覆盖吗!  实现起来也不难。

网格中的每个点,我们都抽象成网络流中对应的两个点:<i, a>   <i, b>。这两个分出来的点可以这么理解:a点即代表他自己,b点即代表前往的目标。如果我们可以从一个点 i 到达另外一个点 j ,我们就把<i, a> 连向 <j, b>,表明从 i -> j。由于每个点都只能被走一次,所以我们的边权都是1。(流量满了即代表这个点已经被走过,不可再被走)。

最后,考虑如何求得结果。最开始,我们有可以到达的点的数量这么多条边(不可到达的点那条路径根本就不可能是通的,所以直接忽略。连上源汇点只是为了方便懒得特判和寻找对应的编号)。每连上一组 <i, a> <j, b> ,其实就代表着我们把二者的路径合并了。

我们又知道一个定理:最小点(在这个题中是路径)覆盖 = 点数 - 最大独立集 = 点数 - 最小割 = 点数 - 最大流。

所以,将最大流求出来,然后操作一下点数即可。

千少万少,代码不能少

#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define INF (~0u>>1)
#define isdigit(c) ((c)>='0'&&(c)<='9')

inline int read(){
    int x = 0, s = 1;
    char c = getchar();
    while(!isdigit(c)){
        if(c == '-')s = -1;
        c = getchar();
    }
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = getchar();
    }
    return x * s;
}

struct node_bian{
    int u, v, w;
    int next;
}t[N];
int f[N];

int R , C;
int s, ht, fx[5], fy[5];
int n, m;
int deth[N];
int ma[200][200], sum = 0;
int cur[N];


int bian = -1; /*一定从 -1 开始,之后寻找反边的 ^ 操作*/
inline void add(int u, int v, int w){
    t[++bian] = (node_bian){u, v, w, f[u]}, f[u] = bian;
    t[++bian] = (node_bian){v, u, 0, f[v]}, f[v] = bian;
    return ;
}

queue <int> q;
bool bfs(){
    memset(deth, 0, sizeof(deth));
    while(!q.empty())q.pop(); /*记得清空队列*/
    deth[s] = 1;
    q.push(s);
    while(!q.empty()){
        int now = q.front();
        q.pop();
        for(int i = f[now]; ~i;i = t[i].next){
            int v = t[i].v, u = t[i].u;
            if(!deth[v] && t[i].w > 0){ /*w > 0 不可忘*/
                deth[v] = deth[u] + 1;
                q.push(v);
            }
        }
    }
    return deth[ht] != 0;
}

int dfs(int now, int flow){
    if(now == ht || !flow) return flow;
    for(int& i = cur[now]; ~i;i = t[i].next){
        int v = t[i].v, u = t[i].u, w = t[i].w;
        if(deth[v] == deth[u] + 1 && w > 0){
            int di = dfs(v, min(flow, w));
            if(di > 0){
                t[i].w -= di;
                t[i^1].w += di;
                return di; 
            }
        }
    }
    return 0;
}

int Dicnic(){
    int ans = 0;
    while(bfs()){
        memcpy(cur, f, sizeof(cur));
        while(int temp = dfs(s, INF)){
            ans += temp;
        }
    }
    return ans;
}

inline void clean(){ /*记得初始化*/
    memset(f, -1, sizeof(f));
    for(register int i = 1;i <= N; i++)t[i].next = -1;
    return ;
}

int main(){
    clean();
    m = read(), n = read(), R = read(), C = read();
    for(int i = 1;i <= m; i++)
        for(int j = 1;j <= n;j++){
            char c;
            cin >> c;
            ma[i][j] = (c == '.');
            if(ma[i][j])sum++;
        }
    fx[1] = R,fy[1] = C, fx[2] = R, fy[2] = -C, fx[3] = C, fy[3] = R, fx[4] = C, fy[4] = -R;/*分清楚方向*/
    s = 2 * m * n + 1, ht = s + 1;
    for(int i = 1;i <= m; i++){
        for(int j = 1;j <= n; j++){
            add(s, (i - 1) * n + j, 1);
            add((i - 1) * n + j + m * n, ht, 1);
            if(ma[i][j]){
                for(int k = 1;k <= 4; k++){
                    int r = i + fx[k], c = j + fy[k]; /*注意,x 对应的是 m, y对应的是 n,搞错就完蛋*/
                    if(r >= 1 && r <= m && c >= 1 && c <= n && ma[r][c]) 
                        add((i - 1) * n + j, (r - 1) * n + c + m * n, 1);/*这里是以坐标来进行编号的*/
                }
            }
        }
    }
    printf("%d\n", sum - Dicnic()); 
    return 0;
}

(说实话,这题的标签怪吓人的)

猜你喜欢

转载自www.cnblogs.com/wondering-world/p/12787597.html