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 |
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矩阵翻转问题还是要冷静分析,抓住每一个可操作点最多只操作一次,再分析一下有关性质就可以了
总之,思路要灵活,实在不行就写暴力。。。