[CCO 2019] Sirtet
- description
- solution
- code
description
题目链接
solution
很巧妙地将差分约束隐藏起来
问题的关键在于求出每一个sand停止运动的时间,这样很容易填涂出最后的答案(向下平移即可)
不妨设 ti,jt_{i,j}ti,j 表示 (i,j)(i,j)(i,j) 位置上的 sand\text{sand}sand 停止运动的时间
-
如果 (i1,j1)(i_1,j_1)(i1,j1) 和 (i2,j2)(i_2,j_2)(i2,j2) 的 sand\text{sand}sand 是连在一起的,则 ti1,j1=ti2,j2t_{i_1,j_1}=t_{i_2,j_2}ti1,j1=ti2,j2
转化成差分约束的形式,即 ti1,j1−ti2,j2≤0,ti2,j2−ti1,j1≤0t_{i_1,j_1}-t_{i_2,j_2}\le 0,t_{i_2,j_2}-t_{i_1,j_1}\le 0ti1,j1−ti2,j2≤0,ti2,j2−ti1,j1≤0
显然,这是具有传递性的【在代码实现中,我选择了同一连通块缩点的方法,利用并查集】
-
如果 (i1,j1)(i_1,j_1)(i1,j1) 和 (i2,j2)(i_2,j_2)(i2,j2) 的 sand\text{sand}sand 不是连在一起的,显然只有 j1=j2j_1=j_2j1=j2 【同一列】的 sand\text{sand}sand 会相互影响
假设 i1<i2i_1<i_2i1<i2 ,显然最多经过 i2−i1−1i_2-i_1-1i2−i1−1 的时间后就一定会碰上【可能i1i_1i1所属的连通块的某个 sand\text{sand}sand 会与其余连通块先撞上】,即 ti1,j1−ti2,j2≤i2−i1−1t_{i_1,j_1}-t_{i_2,j_2}\le i_2-i_1-1ti1,j1−ti2,j2≤i2−i1−1
注意:同一列只需要相邻的两个不同连通块的 sand\text{sand}sand 进行连边【具有传递性】,且最下面的 sand\text{sand}sand 与空【定义为000】的距离为 n−in-in−i
最后求不同连通块之间的最短路即可
code
#include <queue>
#include <cstdio>
#include <vector>
using namespace std;
#define maxn 1000005
#define Pair pair < int, int >
priority_queue < Pair, vector < Pair >, greater < Pair > > q;
vector < Pair > G[maxn];
int n, m;
char **ch, **ans;
int *lst;
int dis[maxn], f[maxn];
bool vis[maxn];int find( int x ) { return f[x] == x ? x : f[x] = find( f[x] ); }void merge( int u, int v ) {u = find( u ), v = find( v );if( u == v ) return;else f[v] = u;
}void addedge( int u, int v, int w ) { u = find( u ), v = find( v );G[u].push_back( { v, w } );
}int id( int x, int y ) { return ( x - 1 ) * m + y; }int main() {scanf( "%d %d", &n, &m );ch = new char * [n + 5];lst = new int [m + 5];for( int i = 1;i <= n;i ++ ) {ch[i] = new char[m + 5];scanf( "%s", ch[i] + 1 );}for( int i = 1;i <= n * m;i ++ ) f[i] = i;for( int i = 1;i <= n;i ++ ) {for( int j = 1;j <= m;j ++ ) {lst[j] = 0; if( ch[i][j] == '#' ) {if( i > 1 and ch[i - 1][j] == '#' ) merge( id( i, j ), id( i - 1, j ) );if( i < n and ch[i + 1][j] == '#' )merge( id( i, j ), id( i + 1, j ) );if( j > 1 and ch[i][j - 1] == '#' )merge( id( i, j ), id( i, j - 1 ) );if( j < m and ch[i][j + 1] == '#' )merge( id( i, j ), id( i, j + 1 ) );}}}for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )if( ch[i][j] == '#' ) {if( lst[j] ) addedge( id( i, j ), id( lst[j], j ), i - lst[j] - 1 );lst[j] = i;}for( int i = 1;i <= m;i ++ ) if( lst[i] ) addedge( 0, id( lst[i], i ), n - lst[i] );for( int i = 1;i <= n * m;i ++ ) dis[i] = 0x3f3f3f3f;q.push( { 0, 0 } );while( ! q.empty() ) {int u = q.top().second; q.pop();if( vis[u] ) continue;vis[u] = 1;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first, w = G[u][i].second;if( dis[v] > dis[u] + w ) {dis[v] = dis[u] + w;q.push( { dis[v], v } );}}}ans = new char * [n + 5];for( int i = 1;i <= n;i ++ ) {ans[i] = new char [m + 5];for( int j = 1;j <= m;j ++ ) ans[i][j] = '.';}for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )if( ch[i][j] == '#' ) ans[i + dis[find( id( i, j ) )]][j] = '#';for( int i = 1;i <= n;i ++ ) {for( int j = 1;j <= m;j ++ )printf( "%c", ans[i][j] );printf( "\n" );}return 0;
}