「CF1718E」Impressionism

题目

点这里看题目。


Burenka 有两张图片 $ a $ 和 $ b$,它们的大小可以表示为 $ n \times m $ 的像素组合。每幅画的每个像素都有一个颜色——表示为一个从 $0 $ 到 \(2 \times 10^5\) 的数字,并且在两幅画的每一行或每一列中都没有重复的颜色,除了颜色 $0 $。

Burenka 想把图片 \(a\) 变成图片 \(b\)。为了实现她的目标,Burenka 可以执行 \(2\) 种操作之一:交换 \(a\) 的任意两行或其任意两列。现在需要你来告诉 Burenka 该如何操作!

行从上到下被编号为 $ 1 $ 到 $ n $ ,列从左到右被编号为 $ 1 $ 到 $ m $。

所有数据满足 \(1\le n,m,n\times m\le 2\times 10^5\)

分析

很特别的题目。

最重要的就是找到一个合适的方法描述这个问题。我们相当于要找到 \(p\in S_n,q\in S_m\),并对于行作用置换 \(p\)、对于列作用置换 \(q\),要求产生的矩阵 \(=b\)

考虑网格的一种转化方式:行作为左部点,列作为右部点;若有非零元素 \(a_{i,j}\),则在左部 \(i\) 和右部 \(j\) 之间连一条颜色为 \(a_{i,j}\) 的边。记 \(a\) 产生的二分图为 \(G_a\)\(b\) 产生的二分图为 \(G_b\)。这样,存在需要的置换 \((p,q)\) 就等价于 \(G_a,G_b\) 作为二分图同构

如何判定?不妨假设 \(n\le m\)。枚举 \(G_a\) 的左部点 \(x\)\(G_b\) 的右部点 \(y\),检查它俩能不能匹配上。首先需要检查二者的出边的颜色集合是否相同;其次,因为一种颜色的出边至多只有一种,所以同颜色出边的终点一定会匹配在一起,接着就是递归检查。如果最终检查结果为 positive,则我们相当于找到了 \(x\) 所在连通块和 \(y\) 所在连通块的匹配方案,贪心地直接匹配上就好,否则就什么也干不了。这样一次检查的复杂度线性于 \(x\) 所在连通块的边数加 \(y\) 所在连通块的边数。

这样做复杂度还是不太友好。一个简单的剪枝是:如果 \(x\) 所在连通块和 \(y\) 所在连通块的左右二部的点数不同,那么一定匹配不上,无需检查。

这样再来分析复杂度——设 \(G_b\) 中连通块为 \((n_1,m_1),(n_2,m_2),\dots,(n_k,m_k)\)\((n,m)\) 表示一个 \(n\) 个左部点、\(m\) 条边的连通块),则复杂度不超过 \(O(\sum_i n_i^2m_i\cdot \frac{n}{n_i})=O(n\sum_i n_im_i)=O(n^2m)\),在 \(n\le m\) 时就有 \(O(n^2m)=O((nm)^{1.5})\)

代码

#include <bits/stdc++.h>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int MAXN = 2e5 + 5;

template<typename _T>
inline void Read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) Write( x / 10 );
    putchar( x % 10 + '0' );
}

typedef std :: pair<int, int> Edge;
typedef std :: pair<int, int> Candidate;
 
struct DSU {
    int fa[MAXN << 1], siz1[MAXN << 1], siz2[MAXN << 1];

    void MakeSet( const int &n, const int &m ) {
        rep( i, 1, n ) fa[i] = i, siz1[i] = 1, siz2[i] = 0;
        rep( i, n + 1, n + m ) fa[i] = i, siz1[i] = 0, siz2[i] = 1;
    }

    int FindSet( const int &u ) {
        return fa[u] == u ? u : ( fa[u] = FindSet( fa[u] ) );
    }

    inline void UnionSet( int u, int v ) {
        u = FindSet( u ), v = FindSet( v );
        if( u == v ) return ;
        fa[u] = v, siz1[v] += siz1[u], siz2[v] += siz2[u];
    }

    inline std :: pair<int, int> GetSize( int u ) {
        u = FindSet( u );
        return std :: make_pair( siz1[u], siz2[u] );
    }
};

std :: tuple<int, int, int> ans[MAXN << 1];
int cnt = 0;

int seq[MAXN];
int matA[MAXN << 1], matB[MAXN << 1];
bool vis[MAXN << 1];

DSU dsuA, dsuB;
std :: vector<Edge> grphA[MAXN << 1], grphB[MAXN << 1];
int timA[MAXN << 1], timB[MAXN << 1], curTim;

Candidate candi[MAXN << 1]; int tot = 0;

int A[MAXN], B[MAXN];

int N, M;

#define GetId( x, y ) ( ( (x) - 1 ) * M + (y) )

inline bool Chk( const int &x, const int &y ) {
    if( matA[x] || matB[y] )
        return matA[x] == y && matB[y] == x;
    if( grphA[x].size() != grphB[y].size() ) return false;
    int n = grphA[x].size();
    rep( i, 0, n - 1 )
        if( grphA[x][i].first != grphB[y][i].first )
            return false;
    candi[++ tot] = { x, y };
    matA[x] = y, matB[y] = x;
    rep( i, 0, n - 1 )
        if( ! Chk( grphA[x][i].second, grphB[y][i].second ) )
            return false;
    return true;
}

int main() {
    // freopen( "impression.in", "r", stdin );
    // freopen( "impression.out", "w", stdout );
    bool swp = false;
    Read( N ), Read( M );
    if( N <= M ) {
        rep( i, 1, N ) rep( j, 1, M )
            Read( A[GetId( i, j )] );
        rep( i, 1, N ) rep( j, 1, M )
            Read( B[GetId( i, j )] );
    } else {
        swp = true;
        std :: swap( N, M );
        rep( i, 1, M ) rep( j, 1, N )
            Read( A[GetId( j, i )] );
        rep( i, 1, M ) rep( j, 1, N )
            Read( B[GetId( j, i )] );
    }
    dsuA.MakeSet( N, M );
    dsuB.MakeSet( N, M );
    rep( i, 1, N ) rep( j, 1, M )
        if( A[GetId( i, j )] ) {
            int c = A[GetId( i, j )];
            grphA[i].emplace_back( c, j + N );
            grphA[j + N].emplace_back( c, i );
            dsuA.UnionSet( i, j + N );
        }
    rep( i, 1, N ) rep( j, 1, M )
        if( B[GetId( i, j )] ) {
            int c = B[GetId( i, j )];
            grphB[i].emplace_back( c, j + N );
            grphB[j + N].emplace_back( c, i );
            dsuB.UnionSet( i, j + N );
        }
    rep( i, 1, N + M ) std :: sort( grphA[i].begin(), grphA[i].end() );
    rep( i, 1, N + M ) std :: sort( grphB[i].begin(), grphB[i].end() );
    rep( i, 1, N ) {
        if( matA[i] ) continue;
        bool any = false;
        rep( j, 1, N )
            if( ! matB[j] && dsuA.GetSize( i ) == dsuB.GetSize( j ) ) {
                tot = 0, curTim ++;
                if( Chk( i, j ) ) {
                    any = true; break;
                } else
                    rep( k, 1, tot )
                        matA[candi[k].first] = matB[candi[k].second] = 0;
            }
        if( ! any )
            return puts( "-1" ), 0;
    }
    for( int i = N + 1, j = N + 1 ; i <= N + M ; i ++ ) {
        if( matA[i] ) continue;
        while( matB[j] ) j ++;
        matA[i] = j, matB[j] = i;
    }
    for( int i = 1 ; i <= N ; i ++ ) {
        seq[0] = 0;
        for( int u = i ; ! vis[u] ; u = matB[u] )
            seq[++ seq[0]] = u, vis[u] = true;
        int opt = 1 + swp;
        rep( j, 1, seq[0] - 1 )
            ans[++ cnt] = std :: make_tuple( opt, seq[j], seq[j + 1] );
    }
    for( int i = 1 ; i <= M ; i ++ ) {
        seq[0] = 0;
        for( int u = i + N ; ! vis[u] ; u = matB[u] )
            seq[++ seq[0]] = u - N, vis[u] = true;
        int opt = 2 - swp;
        rep( j, 1, seq[0] - 1 )
            ans[++ cnt] = std :: make_tuple( opt, seq[j], seq[j + 1] );
    }
    Write( cnt ), putchar( '\n' );
    rep( i, 1, cnt )
        printf( "%d %d %d\n", std :: get<0>( ans[i] ),
                              std :: get<1>( ans[i] ),
                              std :: get<2>( ans[i] ) );
    return 0;
}
posted @ 2023-05-18 23:13  crashed  阅读(34)  评论(0编辑  收藏  举报