[CodeJam 2021 Round 3] Square Free(调整法 / 字典序最小解网络流)

CodeJam 2021 Round3 Square Free

problem

神奈子是个很爱打麻将的老婆婆,有一天她把她的麻将放进了一个 n × m n\times m n×m 的网格图里,每个麻将可以左斜着放入网格中(如 / ),也可以右斜着(如 \ ),如下图所示。不过她还没决定好应该怎么摆。

在这里插入图片描述

因为她很喜欢风,所以摆麻将的地方风很大,容易把麻将吹倒,因此存在两个序列 a a a b b b,表示第 i i i 行必须有恰好 a i a_i ai 个麻将左斜,第 j j j 列必须有恰好 b j b_j bj 个麻将左斜,才能保证麻将的稳定性。

因为她很喜欢山,所以她喜欢山一般的参差不齐感,而不希望麻将形成规规整整的正方形(如下图所
示)。

在这里插入图片描述

她非常没耐心,所以你需要告诉她能否将麻将摆成符合要求的样子,如果可以,为了节省她的时间,你只需要任意给出一种方案即可 。

n , m ≤ 20 n,m\le 20 n,m20

solution

20简直就是各种算法和高复杂度爆艹

在考场上的 调整法 竟然过了,我也不知道对不对。 还是分享给大家。

这种给出任意一种方案,一般都是猜结论或者单纯构造。

这里我从构造入手。

先考虑行的限制,每行从第一列开始直接就放 a i a_i ai/,剩下的放 \。每一行形如 //...//\\..\

这样首先是将行限制全都满足了,在此基础上继续考虑列的限制。

从左往右每一列每一列的考虑,考虑到第 i i i 列的时候,要求前 i − 1 i-1 i1 列全都已经符合要求。

  • 该列的 / 恰好满足要求那就直接下一列。

  • 该列的 / 大于要求。那么就需要将该列上的某几行 / 替换成 \

    直接枚举每一行 k k k ,然后再枚举 k k k 行后面 j ( j > i ) j(j>i) j(j>i) 列的情况。

    如果 k k k i i i 列恰好是 /,且 k k k 行某个 j j j 列恰好是 \,那么就可以交换两个位置上的字符。

    k k k 行的限制仍然满足,也没有改动前 i − 1 i-1 i1 列。

    后面列的改变后面调整再说,现在只对前面负责。

  • 该列的 / 小于要求。与上面同理,需要将该列上的某几行 \ 替换成 /。不赘述。

以上是第一步调整。有可能还不行,就需要进行第二步调整。

以该列的 / 大于要求为例,可能会出现该列上的每个 \ 所在行的 ∀ j , j > i \forall j,j>i j,j>i 列都是 \

那么就找不到可以与该位置交换的 / 了。

这个时候就只能动前面的了。

考虑 i i i 列的某行 j 1 j_1 j1,该 ( j 1 , i ) (j_1,i) (j1,i) 格子字符为 /,考虑现在将其调整为 \

那么首先找到 j 1 j_1 j1 行的 x ( x < i ) x(x<i) x(x<i) 列, ( j 1 , x ) (j_1,x) (j1,x) 格子字符为 /

如果这两个交换,则第 i i i 列的 / 数量就会减少 1 1 1,且 j 1 j_1 j1 行的限制没有破坏。

此时发现, x x x 列的限制破环了,再找一行 j 2 j_2 j2 ( j 2 , x ) (j_2,x) (j2,x) 上的字符为 \,将其改变成 /,又重新维护了 x x x 列的限制。

又发现 j 2 j_2 j2 行的限制被破坏,又得找一个 y y y 列,继续调整 . . . . . . ...... ......

会发现 j 2 j_2 j2 行后面 y ( y > i ) y(y>i) y(y>i) 列一定有 \

因为既然能进入到第二步的调整,那就证明任意行已经找不到 /

将其改变为 /,那么 j 2 j_2 j2 行的限制就又重新维护了。

不管 i i i 列以后的事情,那么所有行和前面所有列的限制仍然成立。

以该列的 / 小于要求同理,也不赘述。

两步调整后,会发现,如果只有第一步调整是永远不会出现正方形的,可是有第二步后就不能保证了。

所以进行第三步对正方形的调整。

而且上述调整发现只可能(?)出现边长为 1 1 1 的正方形。

因为上述调整是行列从小到大的。

一定是先将上一行想尽办法的调整了再调整下一行,所以不可能下一行又折回去这种感觉。

直接枚举每个位置为左上角格子,然后判断一个 2 × 2 2\times 2 2×2 格子是否构成正方形,如果是那么交换两行,注意此时不能直接往下枚举。

i.e.

/\
/\
\/

有可能换了后跟上面的重新组成了一个正方形,那就行减减,再判断一遍。

最后三步调整后再判断一下是否又正方形的出现,这是还出现就直接判为 IMPOSSIBLE

很有可能是因为数据太水,导致我错误地艹过去了。哈哈哈哈


以下就是正解算法。

发现只要存在一个满足行列限制的方案,就一定存在没有正方形的方案。

如果存在正方形,可以将正方形所有格子内的字符全反转,仍然满足行列限制,但破环了正方形。

可以证明这样是不会存在环,无限循环下去的。

证明:

将表格字符压成一维的字符串表示,假设 / 的字典序大于 \

那么每次反转一个正方形后,字典序只会变小。

因此字典序最小的方案一定是不存在正方形的。

问题转化为构造一个字典序最小的方案数。

再加上这个行列限制,明显就是网络流经典问题。

具体可见 code-std 实现。

code

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 25
int n, m;
int r[maxn], c[maxn];
//int ROW[maxn], COL[maxn];
int ans[maxn][maxn];

int main() {
    freopen( "net.in", "r", stdin );
    freopen( "net.out", "w", stdout );
    scanf( "%d %d", &n, &m );
    for( int i = 1;i <= n;i ++ ) scanf( "%d", &r[i] );
    for( int i = 1;i <= m;i ++ ) scanf( "%d", &c[i] );
    for( int i = 1;i <= n;i ++ )
        for( int j = 1;j <= r[i];j ++ )
            ans[i][j] = 1;
    for( int j = 1;j <= m;j ++ ) {
        int cnt = 0;
        for( int i = 1;i <= n;i ++ )
            cnt += ans[i][j];
        if( cnt == c[j] ) continue;
        if( cnt > c[j] ) {
            for( int i = 1;i <= n and cnt != c[j];i ++ )
                if( ans[i][j] ) {
                    for( int k = m;k > j;k -- )
                        if( ! ans[i][k] ) {
                            swap( ans[i][j], ans[i][k] );
                            cnt --;
                            break;
                        }
                }
            for( int i = 1;i <= n and cnt != c[j];i ++ )
                if( ans[i][j] ) {
                    for( int k = 1;k <= n;k ++ ) {
                        for( int w = j - 1;w and i != k;w -- )
                            if( ans[k][w] and ! ans[i][w] )
                                for( int x = m;x > j;x -- )
                                    if( ! ans[k][x] ) {
                                        cnt --;
                                        ans[i][j] = 0;
                                        ans[i][w] = 1;
                                        ans[k][w] = 0;
                                        ans[k][x] = 1;
                                        goto nxtj;
                                    }
                    }
                    nxtj:;
                }
        }
        else {
            for( int i = 1;i <= n and cnt != c[j];i ++ )
                if( ! ans[i][j] ) {
                    for( int k = m;k > j;k -- )
                        if( ans[i][k] ) {
                            swap( ans[i][j], ans[i][k] );
                            cnt ++;
                            break;
                        }
                }
            for( int i = 1;i <= n and cnt != c[j];i ++ )
                if( ! ans[i][j] ) {
                    for( int k = 1;k <= n;k ++ ) {
                        for( int w = j - 1;w and i != k;w -- )
                            if( ans[i][w] and ! ans [k][w] )
                                for( int x = m;x > j;x -- )
                                    if( ans[k][x] ) {
                                        cnt ++;
                                        ans[i][j] = 1;
                                        ans[i][w] = 0;
                                        ans[k][w] = 1;
                                        ans[k][x] = 0;
                                        goto nxti;
                                    }
                    }
                    nxti :;
                }
        }
        // printf( "START: %d\n", j );
        // for( int i = 1;i <= n;i ++ ) {
        //     for( int j = 1;j <= m;j ++ )
        //         if( ans[i][j] ) printf( "%c", 47 );
        //         else printf( "%c", 92 );
        //     puts("");
        // }
        if( cnt != c[j] ) return ! printf( "IMPOSSIBLE\n" );
    }
    for( int i = 1;i < n;i ++ )
        for( int j = 1;j < m;j ++ )
            if( ans[i][j] and ! ans[i][j + 1] and ! ans[i + 1][j] and ans[i + 1][j + 1] ) {
                swap( ans[i][j], ans[i + 1][j] );
                swap( ans[i][j + 1], ans[i + 1][j + 1] );
                i -= 2;
                break;
            }
    for( int i = 1;i < n;i ++ )
        for( int j = 1;j < m;j ++ )
            if( ans[i][j] and ! ans[i][j + 1] and ! ans[i + 1][j] and ans[i + 1][j + 1] )
                return ! printf( "IMPOSSIBLE\n" );
    printf( "POSSIBLE\n" );
    for( int i = 1;i <= n;i ++ ) {
        for( int j = 1;j <= m;j ++ )
            if( ans[i][j] ) printf( "%c", 47 );
            // ROW[i] ++, COL[j] ++;
            else printf( "%c", 92 );
        puts("");
    }
    // for( int i = 1;i <= n;i ++ ) if( ROW[i] != r[i] ) return ! printf( "WA\n" );
    // for( int i = 1;i <= m;i ++ ) if( COL[i] != c[i] ) return ! printf( "WA\n" );
    return 0;
}

code-std

#include <bits/stdc++.h>
using namespace std;
#define maxn 25
int a[maxn], b[maxn], r[maxn], c[maxn];
int n, m;

bool check( int x, int y ) {
    memcpy( r, a, sizeof( a ) );
    memcpy( c, b, sizeof( b ) );
    for( int i = x;i <= n;i ++ ) {
        vector < pair < int, int > > v;
        for( int j = ( i == x ? y : 1 );j <= m;j ++ )
            v.push_back( { b[j], j } );
        sort( v.begin(), v.end() );
        for( int k = v.size() - 1;~ k and a[i];k -- )
            a[i] --, b[v[k].second] --;
    }
    bool flag = 1;
    for( int i = 1;i <= n;i ++ ) flag &= a[i] == 0;
    for( int i = 1;i <= m;i ++ ) flag &= b[i] == 0;
    memcpy( a, r, sizeof( r ) );
    memcpy( b, c, sizeof( c ) );
    return flag;
}

int main() {
    scanf( "%d %d", &n, &m );
    int sum = 0;
    for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] ), sum += a[i];
    for( int i = 1;i <= m;i ++ ) scanf( "%d", &b[i] ), sum -= b[i];
    if( sum ) return ! puts( "IMPOSSIBLE" );
    if( ! check( 1, 1 ) ) return ! puts( "IMPOSSIBLE" );
    puts( "POSSIBLE" );
    for( int i = 1;i <= n;i ++ ) {
        for( int j = 1;j <= m;j ++ )
            if( check( i, j + 1 ) ) printf( "\\" );
            else a[i] --, b[j] --, printf( "/" );
        puts("");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值