[CQOI2017]老C的方块 题解

题意:

老C是个程序员。
游戏被限定在一个由小方格排成的R行C列网格上,有些相邻的小方格之间的公共边比较特殊。
特殊的公共边排列得有很强的规律,下图所示是一个R=C=8的网格,蓝色标注的边是特殊边。
如果网格的规模更大,我们可以用同样的方法找出所有的特殊边。
网格的每个小方格刚好可以放入一个小方块,在游戏的一开始,有些小方格已经放上了小方块,另外的小方格没有放。
有一些小方块排列成了它讨厌的形状(特殊边的位置也要如图中所示),即使是经过任意次旋转、翻转。
为了防止弃疗,老C决定移除一些格子里小方块,使得剩下的小方块不能构成它讨厌的形状。
但是游戏里每移除一个方块都是要花费一些金币的,老C当然希望尽可能少的使用游戏里的金币。

题解:

网络流。
首先,观察题目中的图
1752467-20190824205928579-811636803.png

1752467-20190824205932999-1872504892.png

可以发现,蓝边周围的两个点,要么破坏一个,要么破坏周围的。而周围的都满足距离为奇数。
看到这个条件,又是网格图,可以黑白染色。
这样,周围的点的颜色一定不同,可以建二分图,使用最小割。
然而,有一个“要么破坏一个,要么破坏周围的。”的要求,即“或”的限制。
在最小割中,“或”就是串联,这个割掉一个就破坏了路径。
而周围的破坏所有才行,即“与”,就是并联。
把中间两个点连min的边,周围的点并联,再串联到中间的点上。
如图
1752467-20190824210742454-1714903701.png

最小割即可,跑不满,所以能过。
注意建图细节。

代码:

#include <stdio.h> 
#include <map> 
using namespace std;
#define N 200010
#define M 1000010
#define min(a, b) a < b ? a: b
#define ll long long 
int fr[N],ne[M],v[M],w[M],bs = 0;
void add(int a, int b, int c) {
    v[bs] = b;
    w[bs] = c;
    ne[bs] = fr[a];
    fr[a] = bs++;
}
int dl[N],jl[N],dy[N],S,T,n,inf = 2000000000;
bool bk[N];
bool bfs() {
    for (int i = 1; i <= n; i++) {
        bk[i] = false;
        jl[i] = inf;
    }
    int he = 0, ta = 1;
    dl[he] = S;
    bk[S] = true;
    jl[S] = 0;
    while (he < ta) {
        int u = dl[he];
        for (int i = fr[u]; i != -1; i = ne[i]) {
            if (w[i] > 0 && !bk[v[i]]) {
                bk[v[i]] = true;
                jl[v[i]] = jl[u] + 1;
                dl[ta++] = v[i];
            }
        }
        he += 1;
    }
    return jl[T] < inf;
}
int dfs(int u, int z) {
    if (u == T) return z;
    for (int & i = dy[u]; i != -1; i = ne[i]) {
        if (w[i] > 0 && jl[v[i]] == jl[u] + 1) {
            int rt = dfs(v[i], min(w[i], z));
            if (rt != -1) {
                w[i] -= rt;
                w[i ^ 1] += rt;
                return rt;
            }
        }
    }
    return - 1;
}
int dinic() {
    int jg = 0;
    while (bfs()) {
        for (int i = 1; i <= n; i++) dy[i] = fr[i];
        while (1) {
            int rt = dfs(S, inf);
            if (rt == -1) break;
            jg += rt;
        }
    }
    return jg;
}
void addb(int a, int b, int c) {
    add(a, b, c);
    add(b, a, 0);
}
int x[100010],y[100010],z[100010],fx[6][2] = {-1,0,1,0,0,-1,-1,1,1,1,0,2},r,c;
map < ll,int > mp;
bool check(int x, int y) {
    if (x % 2 == 1) return y % 4 == 1;
    else return y % 4 == 3;
}
int getwz(int x, int y) {
    if (x <= 0 || x > r || y <= 0 || y > c) return - 1;
    ll t = 1ll * x * c + y;
    if (mp.count(t) == 0) return - 1;
    return mp[t];
}
void link(int z1, int z2, int a, int b) {
    addb(a, b, min(z[z1], z[z2]));
    for (int s = 0; s < 6; s++) {
        int tx = x[z1] + fx[s][0],
        ty = y[z1] + fx[s][1];
        int wz = getwz(tx, ty);
        if (wz == -1) continue;
        if ((tx + ty) % 2 == 0) addb(wz + 3, a, inf);
        else addb(b, wz + 3, inf);
    }
}
int main() {
    int m;
    scanf("%d%d%d", &c, &r, &m);
    for (int i = 0; i < m; i++) scanf("%d%d%d", &y[i], &x[i], &z[i]);
    S = 1;  T = 2;  n = m + 2;
    for (int i = 1; i <= n; i++) fr[i] = -1;
    for (int i = 0; i < m; i++) {
        if ((x[i] + y[i]) % 2 == 0) addb(S, i + 3, z[i]);
        else addb(i + 3, T, z[i]);
        mp[1ll * x[i] * c + y[i]] = i;
    }
    for (int i = 0; i < m; i++) {
        int t;
        if (check(x[i], y[i]) && (t = getwz(x[i], y[i] + 1)) != -1) {
            fr[n + 1] = fr[n + 2] = -1;
            link(i, t, n + 1, n + 2);
            n += 2;
        }
    }
    printf("%d", dinic());
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值