题目
题意概要
有一个
n
×
m
n\times m
n×m 的棋盘,有些格子是红色,另外的是蓝色。每次选择一个红色格子,设其位于第
x
x
x 行第
y
y
y 列,则你可以选择二者之一:将它所在的这一行变为白色;将它所在的这一列变为白色。注意红色格子也会被染成白色,也包括被选中的这个格子。
求一个方案使得结束时白色格子最多。
数据范围与提示
n
m
≤
7
×
1
0
6
nm\le 7\times 10^6
nm≤7×106 。为了方便你定义数组,
max
(
n
,
m
)
≤
2500
\max(n,m)\le 2500
max(n,m)≤2500 。
思路
假设我们选择了一个红色格子,要消灭它这一列。那么我们肯定会希望,在此之前,所有这一列上的红色格子都用于消灭它所在的行。进一步的,所有在这些行上的红格子,又要去消灭这一列……大概就像这样。
我们希望使用左上角那个点来消灭这一列。那么它下面这些点就应该提前去消灭那一行。就像水波一样传开了。不难发现,每个点,只要被波及到,它所在的行和列都会被消灭。除了最特殊的——最开始那一个点的那一行,因为不能有红格子提前消灭这一行,把这个红格子给过早擦掉了。
也没有什么特殊情况。一个点有可能试着去 “激发” 一个已经被 “激发” 过的点,没关系。只要出现这种情况(譬如上图中,靠下的四个点),就随意选择一个顺序,最终的效果都是一样的。
所以,我们假想同一行、同一列的红格子之间存在无向边,那么 每个连通块恰有一行或一列会被浪费,其余都是可行的。
然后嘛,白格子的数量 = = = 总格子数量 − - − ( n − a ) ( m − b ) (n-a)(m-b) (n−a)(m−b),其中 a a a 是有多少个行被涂白, b b b 是有多少个列被涂白。由于我们并不在乎究竟是哪几行,所以我们直接枚举 t o t tot tot 个连通块中有多少个连通块丢失了行就行。
然后是构造方案。就按照我上面说的这样做就行了——每个连通块都不断地 “激发” 别人,除了起点的那一行或列是被禁止的。完了。其实挺简单的哈?
时间复杂度 O ( n m ) \mathcal O(nm) O(nm) 。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int_ readint(){
int_ a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int_ x){
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int MaxN = 2500;
char maze[MaxN][MaxN+5];
int n, m;
bool row[MaxN], col[MaxN];
int dfs(int x,int y){
bool rx = row[x], cy = col[y];
row[x] = col[y] = true;
int res = 0; // how many taps
if(!rx) rep(j,0,m-1)
if(maze[x][j] == 'R')
res += dfs(x,j);
if(!cy) rep(i,0,n-1)
if(maze[i][y] == 'R')
res += dfs(i,y);
if(rx != cy) ++ res;
return res;
}
vector<int> rt;
int forbid_x, forbid_y;
void print(int x,int y){
bool rx = row[x], cy = col[y];
row[x] = col[y] = true;
if(!rx) rep(j,0,m-1)
if(maze[x][j] == 'R')
print(x,j);
if(!cy) rep(i,0,n-1)
if(maze[i][y] == 'R')
print(i,y);
if(x == forbid_x) rx = true;
if(y == forbid_y) cy = true;
if(!rx && cy) printf("X %d %d\n",x+1,y+1);
if(rx && !cy) printf("Y %d %d\n",x+1,y+1);
}
int main(){
n = readint(), m = readint();
rep(i,0,n-1)
scanf("%s",maze[i]);
int tot = 0, a = 0, b = 0;
int calc = 0; // how many operations
rep(i,0,n-1) rep(j,0,m-1)
if(maze[i][j] == 'R' && !row[i] && !col[j]){
calc += dfs(i,j), ++ tot; // new root
rt.push_back(i*m+j);
}
rep(i,0,n-1) a += row[i]; // how many line
rep(j,0,m-1) b += col[j]; // how many column
int ans = 0, ansId = 0;
for(int i=0; i<=tot; ++i){ // i roots choose to only go vertically
int x = n-a+i, y = m-b+(tot-i);
if(ans < n*m-x*y) // cnt_white
ans = n*m-x*y, ansId = i;
}
memset(row,0,n), memset(col,0,m);
printf("%d\n",calc+tot);
for(int i=0; i<ansId; ++i){
forbid_x = rt[i]/m, forbid_y = -1;
print(rt[i]/m,rt[i]%m);
}
for(int i=ansId; i<tot; ++i){
forbid_x = -1, forbid_y = rt[i]%m;
print(rt[i]/m,rt[i]%m);
}
return 0;
}