题解 P8221 [WFOI - 02] I wanna reverse to reserve(翻转)
Icyfires18 · · 题解
题意
对输入矩阵进行行/列翻转操作,使其变换成行值递增且列值相等的矩阵,操作数不超过
思路
这篇题解为赛时思路,灵感来源于魔方,并且部分解释与魔方相关。
下文可能看上去很复杂很高级,其实一点即通,毫无思维难度可言。
Step 1 性质
不难发现,对于任意一个点
能看出,单进行行翻转操作,就相当于上述位置左右互换;单进行列翻转操作,就相当于上述位置上下互换。
现称这四个相对位置为一组变换位,其值在翻转变换中不会改变。
将题意再翻译一下,可以得出最终需要将矩阵中所有的变换位翻转成同一形式。若用
由上文性质和值格式可得,若单组变换位满足其中两值等于
下面我们分类讨论将这五种形式翻转成目标变换位的操作方案。
Step 2 方案
对于一组变换位,定义
在考虑单组变换位的翻转方案时,有两点需要注意:
-
行翻转次数为偶数,列翻转次数不限。因为奇数次行反转会影响同行其它已转好的变换位元素顺序,而已转好的变换位列值相等,翻转没有影响。
-
翻转次数尽量控制在
4 次及以内。因为变换位组数为\frac 1 4 (n×m) ,而总操作次数最多为n×m ,均摊下每组为4 次。
接下来在满足上述条件的前提下考虑前文五种情况(从左到右分别对应上面的矩阵):
-
情况一:上行为
0 ,下行为1 ;方案:
\texttt{DRDR} (无多余翻转); -
情况二:上行为
1 ,下行为0 ;方案:
\texttt{URUR} (无多余翻转); -
情况三:正对角为
0 ,斜对角为1 ;方案:
\texttt{RDRD} (无多余翻转); -
情况四:正对角为
1 ,斜对角为0 ;方案:
\texttt{RURU} (无多余翻转); -
情况五:左列为
1 ,右列为0 ;方案:
\texttt{ULRU} (有多余翻转);
想要真正理解方案妙处的可以手动模拟推演过程(毕竟是结论题)。
至此思路的核心要素已经叙述完毕,然后就是代码实现和细节处理了。
P.S.听说变换完后还可以对操作序列离线处理压一下操作数,或许有所帮助,不过在这里就没必要。
代码
记录
-
枚举变换位可先枚举左上位置点再推导,参考 main 函数里的 for 循环;
-
处理变换位可先用位运算提取信息存入二进制再判断,参考 deal 函数;
-
注意 NO 情况的判断有两处,第一处判断值是否符合,第二处判断个数是否满足,见 flag 变量;
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
const int N=101;
int n,m,cnt,a[N][N];//基础矩阵
string str[N][N];//操作序列
bool flag;//NO标记
inline void fip(int &rer){//快读
register char ch;
for(ch=0;ch<'0'||ch>'9';ch=getchar());
for(rer=0;ch>='0'&&ch<='9';ch=getchar())
rer=(rer<<3)+(rer<<1)+(ch^48);
}
void reverse(int col){//列翻转
register int i,j;
for(i=1,j=n;i<j;++i,--j)
swap(a[i][col],a[j][col]);
}
void deal(int x0,int y0,int x1,int y1){//变换位处理
register int s=0;
s|=(a[x0][y0]==y0? 0:1),s<<=1;
s|=(a[x1][y0]==y0? 0:1),s<<=1;
s|=(a[x0][y1]==y0? 0:1),s<<=1;
s|=(a[x1][y1]==y0? 0:1);
if(s==0x3)return;//0011
else cnt+=4;
switch(s){
case 0x5:str[x0][y0]="DRDR";return;//0101
case 0x6:str[x0][y0]="RDRD";return;//0110
case 0x9:str[x0][y0]="RURU";return;//1001
case 0xA:str[x0][y0]="URUR";return;//1010
case 0xC:reverse(y0),reverse(y1);
str[x0][y0]="ULRU";return;//1100
default:flag=true;return;
}
}
void print(int x0,int y0,int x1,int y1){//操作输出
for(auto i:str[x0][y0]){
if(i=='U')printf("0 %d\n",x0);
if(i=='D')printf("0 %d\n",x1);
if(i=='L')printf("1 %d\n",y0);
if(i=='R')printf("1 %d\n",y1);
}
}
int main(){
register int i,j,k;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i)
for(j=1;j<=m;++j)
fip(k),a[i][j]=k,
flag|=(k!=j&&k!=m+1-j);
if(flag){puts("NO");return 0;}
for(i=n>>1;i>=1;--i)
for(j=m>>1;j>=1;--j)
deal(i,j,n+1-i,m+1-j);
if(flag){puts("NO");return 0;}
printf("YES\n%d\n",cnt);
for(i=n>>1;i>=1;--i)
for(j=m>>1;j>=1;--j)
print(i,j,n+1-i,m+1-j);
return 0;
}
不错的月赛题目:)