启程的日子( bitbit )
题目描述
构造一些01矩阵,要求1四连通,然后让这些矩阵加加减减得到一个给定的矩阵。构造的矩阵数量应尽量少。
做法
- n = 1或m = 1的时候特判,答案为黑段数和白段数+1取min。
- 可以观察到min(n, m) > 1时答案至多为3。
现在只需要判定答案0,1,2。
答案0,1计算1连通块数量即可判定。
答案2一定是加一块减一块,可以考虑枚举减去的0联通块。记录和这个
联通块相连的1联通块个数就可以判定。时间复杂度O(nm)。注意上述构造在n = 2时会有问题,需要特判。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5010;
int n,m,dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
char s[maxn][maxn],ss[maxn][maxn];
int vs[maxn][maxn],col[maxn*maxn],O;
void dfs(int x,int y,int zz=1){ //将(x,y)所在'0'联通块标记为zz(注意是正数)
vs[x][y]=zz;
for(int i=0;i<4;++i){
int xx=x+dx[i],yy=y+dy[i];
if(xx<1||xx>n||yy<1||yy>m)continue;
if(s[xx][yy]!='1'||vs[xx][yy])continue;
vs[xx][yy]=zz;
dfs(xx,yy,zz);
}
}
void dfs0(int x,int y,int tg){ //将(x,y)所在'1'联通块标记为tg(注意是负数)
vs[x][y]=tg;
for(int i=0;i<4;++i){
int xx=x+dx[i],yy=y+dy[i];
if(xx<1||xx>n||yy<1||yy>m)continue;
if(vs[xx][yy]>0&&col[vs[xx][yy]]!=tg){//标记并统计0连通块周围的1连通块
col[vs[xx][yy]]=tg;
O++;
}
if(s[xx][yy]!='0'||vs[xx][yy])continue;
vs[xx][yy]=tg;
dfs0(xx,yy,tg);
}
}
int sol1(){
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
vs[i][j]=0;
int flg=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)if(s[i][j]=='1'&&!vs[i][j]){
if(flg)return false; //存在两个1的连通块
flg=1;
dfs(i,j);
}
if(flg){ //只存在一个1的连通块
printf("1\n+\n");
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=m;++j)
printf("%c",s[i][j]);
} else { //不存在一个1的连通块
printf("0\n");
}
return true;
}
int sol2(){
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
vs[i][j]=0;
int ptr=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)if(s[i][j]=='1'&&!vs[i][j])
dfs(i,j,++ptr); //标记1连通块
for(int i=1;i<=ptr;++i)col[i]=0;
int tg=-1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)if(s[i][j]=='0'&&!vs[i][j]){
O=0;
dfs0(i,j,--tg); //标记0连通块
if(O==ptr){ //如果统计到当前0连通块周围的1连通块数量等于1连通块的总数量,即该0连通块周围分布了所有的1连通块
printf("2\n+\n");
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=m;++j)
printf("%c",s[i][j]=='1'||vs[i][j]==tg?'1':'0');
printf("-\n");
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=m;++j)
printf("%c",vs[i][j]==tg?'1':'0');
return true;
}
}
// for(int i=1;i<=n;++i,puts(""))
// for(int j=1;j<=m;++j)
// printf("%d ",vs[i][j]);
return false;
}
void sol3(int revflg=0){ //三步操作
printf("3\n+\n");
if(revflg==0){ //1.除了最后一行,加上第一行、奇数列、原有的1
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=m;++j)
printf("%c",i!=n&&(i==1||j%2==1||s[i][j]=='1')?'1':'0');
} else { //根据前面代码,revflg=1时应反转行和列
for(int j=1;j<=m;++j,puts(""))
for(int i=1;i<=n;++i)
printf("%c",i!=n&&(i==1||j%2==1||s[i][j]=='1')?'1':'0');
}
printf("+\n");
if(revflg==0){ //2.除了第一行,加上最后一行、偶数列、原有的1
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=m;++j)
printf("%c",i!=1&&(i==n||j%2==0||s[i][j]=='1')?'1':'0');
} else {
for(int j=1;j<=m;++j,puts(""))
for(int i=1;i<=n;++i)
printf("%c",i!=1&&(i==n||j%2==0||s[i][j]=='1')?'1':'0');
}
printf("-\n");
if(revflg==0){ //3.减去除了第一行和最后一行的原有1以外的其他
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=m;++j)
printf("%c",(i==1||i==n)&&(s[i][j]=='1')?'0':'1');
} else {
for(int j=1;j<=m;++j,puts(""))
for(int i=1;i<=n;++i)
printf("%c",(i==1||i==n)&&(s[i][j]=='1')?'0':'1');
}
}
int pre[maxn];
int sol(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%s",s[i]+1);
if(sol1())return 0; //只有一或零个1的连通块
if(sol2())return 0; //存在某个0连通块,它的周围分布了所有1连通块
int revflg=0;
if(m==1){ //只有1列时转置,方便处理
revflg=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
ss[j][i]=s[i][j];
swap(n,m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
s[i][j]=ss[i][j];
} else if(n==2){ //列数大于一但是只有两行时,转置
revflg=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
ss[j][i]=s[i][j];
swap(n,m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
s[i][j]=ss[i][j];
// for(int i=1;i<=n;++i,puts(""))
// for(int j=1;j<=m;++j)
// printf("%c",s[i][j]);
}
if(n==1){ //如果只有一行
for(int i=1;i<=m;++i)pre[i]=i;
for(int i=2;i<=m;++i)if(s[1][i]==s[1][i-1]){
pre[i]=pre[i-1];
} else pre[i]=i; //标记每个连通块的最左端位置
int cnt=0,cnt2=0;
for(int i=1;i<=m;++i)
if(s[1][i]=='1'&&pre[i]==i)cnt++; //1连通块个数
else if(s[1][i]=='0'&&pre[i]==i)cnt2++; //0连通块个数
printf("%d",min(cnt,cnt2+1)); //要么1的块分别加,要么先全是1再减去0的块
if(cnt<cnt2+1){ //把每个1的块全部加起来
for(int i=1;i<=m;++i)if(s[1][i]=='1'&&pre[i]==i){
printf("\n+\n");
if(revflg==0){
for(int j=1;j<=m;++j)
if(pre[j]==i)printf("1");
else printf("0");
} else {
for(int j=1;j<=m;++j)
if(pre[j]==i)printf("1\n");
else printf("0\n");
}
}
} else { //先全填上1,再减去0的块
printf("\n+\n");
if(revflg==0){
for(int i=1;i<=m;++i)printf("1");
} else {
for(int i=1;i<=m;++i)printf("1\n");
}
for(int i=1;i<=m;++i)if(s[1][i]=='0'&&pre[i]==i){
printf("\n-\n");
if(revflg==0){
for(int j=1;j<=m;++j)
if(pre[j]==i)printf("1");
else printf("0");
} else {
for(int j=1;j<=m;++j)
if(pre[j]==i)printf("1\n");
else printf("0\n");
}
}
}
puts("");
return 0;
}
sol3(revflg);//特判结束,那么只需要3次操作
}
int main(){
freopen("bitbit.in","r",stdin);
freopen("bitbit.out","w",stdout);
sol();
}