[题解] [TJOI2011] 构造矩阵

题面

题解

很容易看出来是道网络流的题目, 要是没有这个字典序最小, 直接建图跑一遍就好了, 考虑如何输出字典序最小的方案

我们可以贪心地去选择, 若当前点可以选0就选0, 不能选0就选1, 有一点像搜索, 但是直接搜索回溯肯定会爆炸, 考虑如何不回溯

用网络流优化, 若当前点选0后面有可行的方案, 直接选0, 若没有可行的方案, 这个点就只能选1了, 考虑到当前点的选择是在前面点已经选择完并确定有了可行解的情况下进行的, 所以这个策略是可行的, 至于时间复杂度的问题, 代码中会讲到

代码

###include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <queue>
#define itn int
#define reaD read
#define N 105
#define INF 1000000009
using namespace std;

int n, m, r[N], c[N], d[N << 1], S, T, head[N << 1], cur[N << 1], cnt = 1, id[N][N], ans[N][N];
struct edge { int to, next, flow; } e[20005]; 

inline int read()
{
    int x = 0, w = 1; char c = getchar();
    while(c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return x * w;
}

inline void add(int u, itn v, int w) { e[++cnt] = (edge) { v, head[u], w }; head[u] = cnt; e[++cnt] = (edge) { u, head[v], 0 }; head[v] = cnt; }

int bfs()
{
    queue<int> q; memset(d, 0, sizeof(d));
    q.push(S); d[S] = 1;
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int v, i = head[u]; i; i = e[i].next)
            if(!d[v = e[i].to] && e[i].flow > 0) { d[v] = d[u] + 1; q.push(v); }
    }
    return d[T]; 
}

int dfs(int u, int a)
{
    if(u == T || !a) return a;
    int flow = 0;
    for(int v, &i = cur[u]; i; i = e[i].next)
    {
        if(d[v = e[i].to] == d[u] + 1 && e[i].flow > 0)
        {
            int f = dfs(v, min(a, e[i].flow));
            flow += f; a -= f; e[i].flow -= f; e[i ^ 1].flow += f; 
        }
        if(!a) break; 
    }
    if(a) d[u] = -1;
    return flow; 
}

int dinic() { int flow = 0; while(bfs()) { memcpy(cur, head, sizeof(head)); flow += dfs(S, INF); } return flow; }

bool check(itn x, int y)
{
    dinic(); if(!r[x] || !c[y]) return 0; r[x]--; c[y]--; 
    //先考虑当前行或列是否已经选完了所有可选的0
    if(e[id[x][y]].flow) { e[id[x][y]].flow = 0; return 1; }//若这条边flow = 1, 代表这条边没有被增广过, 也就是(x, y)这个点是0, 直接断掉这条边(保证以后增广不走他), return即可
    else
    {
        /*若这条边flow = 0, 代表这条边被增广过, 这个点(x, y)当前为1, 考虑将他变成0是否有可行解, 
        由于这条边被增广过, 故必有一条S -> x -> y -> T的增广路, 手动退流, 再次增广, 若能找到一条新的增广路补上这条路断掉之后的影响, 代表点(x, y)可以为0, 否则, 不能为0, 将x -> y断掉, 表示强制走他, S -> x和y -> T这两条边的流量减1即可, 由于没有被增广过, 刚刚正向边加上后流量必然>0, 将正向边流量减1即可*/
        e[id[x][y] ^ 1].flow = 0; e[id[x][m + 1] ^ 1].flow--; e[id[x][m + 1]].flow++;
        e[id[n + 1][y] ^ 1].flow--; e[id[n + 1][y]].flow++; if(dinic() == 1) return 1;
        r[x]++; c[y]++; e[id[n + 1][y]].flow--; e[id[x][m + 1]].flow--; return 0; 
    }
    //由于每次断边后只需要找到一条新的增广路即可, 所以每次dinic只会找到一条增广路, 复杂度是正确的
}

int main()
{
    n = read(); m = read(); S = n + m + 1; T = n + m + 2; 
    for(int i = 1; i <= n; i++) r[i] = read(); 
    for(int i = 1; i <= m; i++) c[i] = read(); 
    for(int i = 1; i <= n; i++)
    {
        id[i][m + 1] = cnt + 1; add(S, i, r[i]); 
        for(int j = 1; j <= m; j++)
        {
            if(i == 1) id[n + 1][j] = cnt + 1, add(j + n, T, c[j]); 
            id[i][j] = cnt + 1; add(i, j + n, 1); 
        }
    }
    for(int i = 1; i <= n; i++) r[i] = m - r[i]; 
    for(int j = 1; j <= m; j++) c[j] = n - c[j]; 
    for(int i = 1; i <= n; i++, puts(""))
        for(int j = 1; j <= m; j++) printf("%d", check(i, j) ? 0 : 1);
    return 0;
}

转载于:https://www.cnblogs.com/ztlztl/p/11007824.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值