SD省队集训2019Day3之“启程的日子”

启程的日子( bitbit )

题目描述

构造一些01矩阵,要求1四连通,然后让这些矩阵加加减减得到一个给定的矩阵。构造的矩阵数量应尽量少。

做法

  1. n = 1或m = 1的时候特判,答案为黑段数和白段数+1取min。
  2. 可以观察到min(n, m) > 1时答案至多为3。

  1. 现在只需要判定答案0,1,2。
    答案0,1计算1连通块数量即可判定。
    答案2一定是加一块减一块,可以考虑枚举减去的0联通块。记录和这个
    联通块相连的1联通块个数就可以判定。时间复杂度O(nm)。

  2. 注意上述构造在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();
}

猜你喜欢

转载自www.cnblogs.com/water-lift/p/10993765.html