LOJ #6068. 「2017 山东一轮集训 Day4」棋盘

#6068. 「2017 山东一轮集训 Day4」棋盘

分析:

  首先将每个整张图按行和列分连通块,两个'#'之间算一个连通块。每个空点只能属于个行连通块和列连通块,从行连通块向列连通块连边,容量为1,费用为0。如果这条边走了1的流量,就说明这个点选了,考虑这个点选了后的花费。

  如果这个点是他属于的行连通块中的第一个点,那么费用为0,如果是第二个,那么费用为1...

  于是S向行连通块连连通块连siz条边,费用分别是0,1,2...siz-1。预处理答案,每次增加一个点,即增加一的总容量,跑费用流。

代码:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<cctype>
#include<set>
#include<queue>
#include<vector>
#include<map>
#include<bitset>
#define fore(i, u, v) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
using namespace std;
typedef long long LL;

inline int read() {
    int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f;
}

const int INF = 1e9, N = 160005;
struct Edge { int from, to, nxt, cap, cost; } e[N << 1];
int head[N], dis[N], pre[N], siz[N], bel[55][55], bel2[55][55], q[N], ans[N];
bool vis[N];
char s[55][55];
int En = 1, S, T, TT;

inline void add_edge(int u,int v,int f,int w) {
    ++En; e[En] = (Edge){u, v, head[u], f, w}; head[u] = En;
    ++En; e[En] = (Edge){v, u, head[v], 0, -w}; head[v] = En;    
}
bool spfa() {
    for (int i = 0; i <= TT; ++i) dis[i] = INF, vis[i] = pre[i] = 0;
    int L = 1, R = 0; q[++R] = S; dis[S] = 0, vis[S] = 1; 
    while (L <= R) {
        int u = q[L ++]; vis[u] = 0;
        fore(i, u, v) 
            if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) {
                dis[v] = dis[u] + e[i].cost, pre[v] = i;
                if (!vis[v]) q[++R] = v, vis[v] = 1;
            }
    }
    return dis[TT] != INF;
}
int mcf() {
    spfa();
    int Flow = INF;
    for (int i = TT; i != S; i = e[pre[i]].from) 
        Flow = min(Flow, e[pre[i]].cap);
    for (int i = TT; i != S; i = e[pre[i]].from) 
        e[pre[i]].cap -= Flow, e[pre[i] ^ 1].cap += Flow;
    return Flow * dis[TT];
}
int main() {
    int n = read(), sum = 0, cnt = 0, tmp;
    for (int i = 1; i <= n; ++i) {
        scanf("%s", s[i] + 1);
        for (int j = 1; j <= n; ++j) sum += s[i][j] == '.';
    }
    for (int i = 1; i <= n; ++i) 
        for (int j = 1; j <= n; ++j) {
            if (j == 1 || s[i][j - 1] == '#') cnt ++;
            bel[i][j] = cnt;
            siz[cnt] ++;
        }
    tmp = cnt;
    for (int j = 1; j <= n; ++j) 
        for (int i = 1; i <= n; ++i) {
            if (i == 1 || s[i - 1][j] == '#') cnt ++;
            bel2[i][j] = cnt;
            siz[cnt] ++;
        }
    S = 0, T = cnt + 1, TT = T + 1;
    for (int i = 1; i <= tmp; ++i) {
        add_edge(S, i, 1, 0);
        for (int j = 1; j < siz[i]; ++j) add_edge(S, i, 1, j);
    }
    for (int i = tmp + 1; i <= cnt; ++i) {
        add_edge(i, T, 1, 0);
        for (int j = 1; j < siz[i]; ++j) add_edge(i, T, 1, j);
    }
    for (int i = 1; i <= n; ++i) 
        for (int j = 1; j <= n; ++j) 
            if (s[i][j] == '.') add_edge(bel[i][j], bel2[i][j], 1, 0);
    for (int i = 1; i <= sum; ++i) {
        add_edge(T, TT, 1, 0);
        ans[i] = ans[i - 1] + mcf();
    }
    for (int m = read(); m --; ) 
        printf("%d\n", ans[read()]);
    return 0;
}

 

转载于:https://www.cnblogs.com/mjtcn/p/10618321.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值