A、变换队形

(transitioning.cpp/c/pas)

【问题描述】

有一个调皮的班级,他们上体育课,现在排成了n行n列的一个矩形。每个人却不是面向老师,而是面向左侧或右侧。例如:

RLR 

RRL

LLR

因为老师是新来的,所以还叫不出学生的名字。他每次都是朝着一列或一行喊:向后转。这一行或这一列的L就全部变成R,R就全部变成L。因为不能让所有学生都朝一个方向,所以体育老师退而求其次,他可以允许一个人和其他人方向相反。体育老师的指令没有次数限制,他也可以对同一行或同一列进行多次发令。请找出这样的一个学生。如果最后无法使得只有一个学生和其他人方向相反,则输出“-1”。如果存在方案,则输出那一个学生的行号和列号(均从1开始)。如果有多种方案,则输出行号最小的那一个学生,如果多个学生的行坐标相等,则输出列号最小的那一个。

【输入格式】

输入文件名为 transitioning.in。

第一行一个整数,表示n。

接下来是n行,每行n个字符,每一个字符为L或R。

【输出格式】

输出文件名为 transitioning.out。

输出文件只有 1 行,表示那个讨厌的学生的行号和列号。

如果没有,则输出-1。

【输入输出样例 】

transitioning.in

transitioning.out

3
RLR
RRL

LLR

1 1

 

 

 

【输入输出样例说明】

在这个例子中,位于第1行第1列(左上角)的学生是那个令人讨厌的学生,因为老师可以喊叫第2行和第3列向后转,只有这一个学生和其他学生朝向不同。

 

N<=1000

 

题解

先按考场上的思路讲

首先发现每一行每一列最多翻转一次

我们可以枚举一下‘那个令人讨厌的学生’的位置

把同行的与他相同的人的所在列翻转,然后把同列的与他相同的人的所在行翻转(保证了他同行同列的人都与他不同)

然后判断一下整个矩阵中他是不是与所有人不同(因为如果还需要翻转,就又会让同行同列的人不满足条件)

然后发现是O(n^4)

就开始想怎么优化

发现把每一行的状态和每一列的状态分别用bitset存起来,用位运算来快速判断翻转后的行或列是否满足条件

但仍然是O(15*n^3)

开始自闭。。。

后来自己手造了一组大样例(全屏L),然后把1000,1000换成R

结果发现秒出1000 1000

冷静分析,发现如果一个位置不是答案,基本上一次可以就把他排除掉,所以时间复杂度在O(15*n^2)左右。。。

其实后来也发现了一些性质,就是如果全图只有一个‘那个令人讨厌的学生’,那么那个位置永远也换不到其他地方(无证明)

但是不知道怎么用。。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
using namespace std;
#define N 1005
char c[N][N];
bitset<N> a[N],b[N];
int main()
{
	freopen("transitioning.in","r",stdin);
	freopen("transitioning.out","w",stdout);
	int n,i,j,k;
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%s",c[i]+1);
		for(j=1;j<=n;j++){
			if(c[i][j]=='L'){
				a[i].set(j);
				b[j].set(i);
			}
		}
	}
	if(n==1){
		printf("1 1");
		return 0;
	}
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			a[i].flip(j);
			b[j].flip(i);
			int flg=0;
			for(k=1;k<=n;k++){
				if(i==k) continue;
				if(c[i][j]==c[k][j]&&(a[i]&a[k]).any()){flg=1;break;}
				if(c[i][j]!=c[k][j]&&(a[i]^a[k]).any()){flg=1;break;}
			}
			for(k=1;k<=n;k++){
				if(j==k) continue;
				if(c[i][j]==c[i][k]&&(b[j]&b[k]).any()){flg=1;break;}
				if(c[i][j]!=c[i][k]&&(b[j]^b[k]).any()){flg=1;break;}
			}
			if(!flg){
				printf("%d %d",i,j);
				return 0;
			}
			a[i].flip(j);
			b[j].flip(i);
		}
	}
	printf("-1");
}

 

正解

直接把最后一行的L位置所在列翻转(R位置也可以),

如果没有‘那个令人讨厌的学生’,则1~n-1行要么全是L,要么全是R

如果有一个,则他就会在那一行显得尤为突出(在那一行与众不同)

记一下突出学生的个数(>1就输出-1)与第一个突出学生的位置,就是答案了

如果突出学生的个数=n-1的话,且都在同一列,就说明‘那个令人讨厌的学生’在最后一行的该列。

(以上是我个人理解,std的实现可能略有不同)(好像std有点问题)

#include<bits/stdc++.h>
using namespace std;
#define MAXN 1005
int n;
char s[MAXN][MAXN];
int cnt[MAXN];
int ansx,ansy,cntR;
int cntx,miny1,miny2;
int main()
{
    freopen("transitioning.in","r",stdin);
    freopen("transitioning.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%s",s[i]+1);
    for(int j=1;j<=n;j++)
    {
      if(s[n][j]=='R')
      cnt[j]^=1;
    }
    for(int i=n-1;i>=1;i--)
    {
        cntR=0;
        for(int j=n;j>=1;j--)
           if(s[i][j]=='R'&&cnt[j]==0||s[i][j]=='L'&&cnt[j]==1)
            cntR++,miny1=j;
           else
           miny2=j;
           if(cntR==0||cntR==n)
           {
             continue;
           }
           else
           {
             if(min(n-cntR,cntR)==1)
             {
               if(cntx>0)
               {
               printf("-1\n");
               return 0;
               }
               else
               {ansx=i;
                if(cntR==1)ansy=miny1;
                else ansy=miny2;
               cntx++;
               }
             }
             else
             {
                printf("-1\n");
                return 0;
             }
           }
    }
    if(ansx==0&&ansy==0)
    printf("-1\n");
    else
    printf("%d %d\n",ansx,ansy);
    return 0;
}

感想

遇到01矩阵翻转问题还是要冷静分析,抓住每一个可操作点最多只操作一次,再分析一下有关性质就可以了

总之,思路要灵活,实在不行就写暴力。。。