一、题目
题目翻译
给你两个 n × m n\times m n×m的矩阵,里面的每一个元素都是 01 01 01串,每次可以把 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)的末尾的字符插入到 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的首位,要求 ( x 1 , y 1 ) ≠ ( x 2 , y 2 ) (x_1,y_1)\not=(x_2,y_2) (x1,y1)=(x2,y2)并且 x 1 = x 2 o r y 1 = y 2 x_1=x_2\space or\space y_1=y_2 x1=x2 or y1=y2,求一个合法的操作序列使得给出的第一个矩阵经过操作后成为第二个矩阵。
数据范围
2 ≤ n , m ≤ 300 2\leq n,m\leq300 2≤n,m≤300,字符总数不超过 1 e 5 1e5 1e5
二、解法
好像没人发题解,那我来发一个。
一开始看到这道题真的没什么思路,但仔细考虑,本题并未要求操作序列长度最小,所以在一定程度上给了我们乱搞的机会。
考虑一种暴力的构造方法,假设我们能够找到一种状态,让第一个矩阵可以到达,第二个矩阵也可以到达,那我们就可以把它当成中转状态,也就是第一个矩阵的操作序列和第二个矩阵的操作序列反向就可以拼出答案。
我们把所有的
1
1
1放在第一行,所有的
0
0
0放在第二行,这样两个矩阵一定能到达这个状态,且是比较容易操作的。过程中我们要保证(x1,y1),(x2,y2)
不同但要有行或列的重合,途中判断一下就行了,看代码吧。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 305;
const int M = 400005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,tot,t,cnt[N][N],ans[M][4];string s;
void ins(int x1,int y1,int x2,int y2)
{
cnt[x2][y2]++;
ans[++tot][0]=x1;ans[tot][1]=y1;
ans[tot][2]=x2;ans[tot][3]=y2;
}
void add(int x,int y,int f)
{
if(f)
{
if(x!=1) ins(x,y,1,y);
else ins(x,y,1,y==1?2:1);
return ;
}
if(x!=2) ins(x,y,2,y);
else ins(x,y,2,y==1?2:1);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>s;
for(int k=s.size()-1;k>=0;k--)
add(i,j,s[k]=='1');
}
for(int i=1;i<=2;i++)
for(int j=2;j<=m;j++)
for(int k=1;k<=cnt[i][j];k++)
ins(i,j,i,1);
t=tot;
//
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>s;
for(int k=0;k<s.size();k++)
//这里要注意,插入顺序要反向
add(i,j,s[k]=='1');
}
for(int i=1;i<=2;i++)
for(int j=2;j<=m;j++)
for(int k=1;k<=cnt[i][j];k++)
ins(i,j,i,1);
//
printf("%d\n",tot);
for(int i=1;i<=t;i++)
printf("%d %d %d %d\n",ans[i][0],ans[i][1],ans[i][2],ans[i][3]);
for(int i=tot;i>t;i--)
printf("%d %d %d %d\n",ans[i][2],ans[i][3],ans[i][0],ans[i][1]);
}