《挑战程序设计竞赛》之“反转”问题总结

《挑战程序设计竞赛》之“反转”问题总结

萌新又来写总结了
“反转”问题在《挑战程序设计竞赛》P150左右的位置~

拒!绝!搜!索!

这类问题有以下几个特征:
1.大多集中在一维/二维两种情形,二维数据范围一般很小(毕竟需要一部分的枚举);是对一个区间/相邻几个格子进行反转;
2.一般用搜索(dfs/bfs)是无法在规定时间内完成的(除非数据太小);
3.每一个格子有两个状态(正/反),区间的反转顺序对最终的结果毫无影响;
4.对某一个格子进行两次以上的反转是多于的,所以只需要%2便可知道反转的结果。

借用一句话:
“反转法跟尺取法有点像,都是限定一个区间,往前挪动,尾部挪动之后,需要减掉尾部的影响。”

这里列举几个经典题目:
一维反转:
Face The Right Way POJ - 3276
The Water Bowls POJ - 3185
二维反转:
Fliptile POJ - 3279
The Pilots Brothers’ refrigerator POJ - 2965
Flip Game POJ - 1753
EXTENDED LIGHTS OUT POJ - 1222

A. Face The Right Way POJ - 3276

tips:我们可以认为,这“反转连续的K头牛”只能按照序号递增的顺序(比如k = 3 ,反转 7 6 8三头连续的牛,可以认为是以6号为起点,连续反转6 7 8 三头牛,顺序毫无影响)。即:问题被转化为求需要反转的区间的集合。
在这里插入图片描述
对于最后不足k头牛,直接进行判断,无需再反转(已经不够k头牛了)。

#include<cstdio>
#include<cstring>

const int maxn = 5005;
int book[maxn],flip[maxn],n,k,m,ans,reck;
char c;

int solve(int k){
	memset(flip,0,sizeof(flip));
	int sum = 0, res = 0;			//res用来统计答案(反转次数)
	for(int i = 1; i <= n - k + 1; ++i){
		if((book[i] + sum) % 2 == 0)	flip[i] = 0;
		else	sum++, res++, flip[i] = 1;
		if(i - k >= 0)	sum -= flip[i - k + 1];	//为下一个位置做准备
	}
	for(int i = n - k + 2; i <= n; ++i){		//直接判断
		if((book[i] + sum) % 2 != 0)	return -1;
		if(i - k >= 0)	sum -= flip[i - k + 1];
	}
	return res;
}

int main(){
	scanf("%d",&n); ans = 1e9;
	for(int i = 1; i <= n; ++i){
		getchar();
		scanf("%c",&c);
		book[i] = (c == 'B' ? 1 : 0);
	}
	for(k = 1; k <= n; ++k){
		int tmp = solve(k); 
		if(tmp != -1 && ans > tmp){
			ans = tmp;
			reck = k;
		}
	}
		
	printf("%d %d\n",reck,ans);
	return 0;
} 

B.The Water Bowls POJ - 3185

tips:和上一个题相似,只是限定了每次反转自己和相邻2个格子。可以直接运算,不用像上一题那样用sum记录。【唯一的区别在于,如果是两头的格子,只能反转2个;中间的格子可以反转3个,k不是固定值
注意分类讨论:第一个格子不反转/第一个格子反转。
我们认为,从最左端开始,当某一个格子定下来之后,只有后一个格子能改变他(因为他前面的和他已经定下来了),因此根据当前格子的状态可以唯一确定下一个格子的反转次数。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int book[25],flip[25],minn,tmp[25];

void solve1(){		//第一个格子不反转
	int cnt = 0;
	memcpy(tmp, book, sizeof(book));
	memset(flip, 0,sizeof(flip));
	for(int i = 2; i <= 20; ++i){
		if((flip[i - 1] + tmp[i - 1]) % 2 == 0)	continue;	//当前格子反转与否,取决于前一个格子的状态
		else	flip[i]++, tmp[i + 1]++, cnt++;	//当前格子反转,下一个格子的 状态也会受影响,用tmp++表示。之所以用memcpy,是不想改变原来的数据
	}
	if((flip[20] + tmp[20]) % 2 == 0)
		minn = min(minn, cnt);
}

void solve2(){		//第一个格子反转
	int cnt = 0;
	memset(flip, 0,sizeof(flip));
	memcpy(tmp, book, sizeof(book));
	flip[1] = 1, tmp[2]++, cnt++;
	for(int i = 2; i <= 20; ++i){
		if((flip[i - 1] + tmp[i - 1]) % 2 == 0)	continue;
		else	flip[i]++, tmp[i + 1]++, cnt++;
	}
	if((flip[20] + tmp[20]) % 2 == 0)
		minn = min(minn, cnt);
}

int main(){
	minn = 1e5;
	for(int i = 1; i <= 20; ++i)
		scanf("%d",&book[i]);
	solve1(); solve2();
	printf("%d",minn);
	return 0; 
} 

/*
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2
*/

也可以借鉴A题的写法,两次处理(第一个元素改/不改)。注意修改最后一个数据时,只能修改2个,所以判断只是对最后一个进行判断,而不是从第n-k+2个。
另一种写法:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn = 25;
int book[maxn],filp[maxn],n,k,minn;

int solve(int k){
	memset(filp,0,sizeof(filp));
	int sum = 0, res = 0;
	for(int i = 1; i <= n - 1; ++i){
		if((book[i] + sum) % 2 == 0)	filp[i] = 0;
		else	sum++, res++, filp[i] = 1;
		if(i - k >= 0)	sum -= filp[i - k + 1];
	}
	return (book[n] + sum) % 2 != 0	? -1 : res;
}

int main(){
	n = 20;
	for(int i = 1; i <= n; ++i)
		scanf("%d",&book[i]);
	int tmp1 = solve(3); 		//k = 3, 第一个不反转
	book[1]++, book[2]++;		//第一个反转相当于次数+1,第1、2个数据+1
	int tmp2 = solve(3) + 1;
	printf("%d",min(tmp1,tmp2));	
	return 0;
} 

C.Fliptile POJ - 3279

非常经典的二维反转。因为n,m最大可以到15,用dfs/bfs必会TLE。
每一个格子可以改变自己和上下左右,所以无法向一维反转那样做(因为能改变(1,1)的还可以有(1,2)和(2,1),而上一个题让最左端牛改变的方法只有一种,因为只有一个区间包含1)。

我们可以枚举第一行的所有反转情况,第一行的反转情况定下后,只有其下一行可以改变上一行的状态,以此类推,直到最后一行,然后检查最后一行是否都为0即可。

tips:状态压缩,用二进制表示集合的枚举。

#include<cstdio>
#include<cstring>
using namespace std;

const int maxn = 20;
int n,m,book[maxn][maxn],flip[maxn][maxn],ans[maxn][maxn];
int dir[5][2] = {{1,0},{-1,0},{0,0},{0,1},{0,-1}};

int getcolor(int x, int y){
	int c = book[x][y];
	for(int k = 0; k < 5; ++k){
		int tx = x + dir[k][0], ty = y + dir[k][1];
		if(tx < 1 || ty < 1 || tx > m || ty > n)	continue;
		c += flip[tx][ty];
	}
	return c % 2;
}

int calc(){
	for(int i = 2; i <= m; ++i)
		for(int j = 1; j <= n; ++j)
			if(getcolor(i - 1, j))	flip[i][j] = 1;
	
	for(int j = 1; j <= n; ++j)
		if(getcolor(m, j))	return -1;
		
	int cnt = 0;
	for(int i = 1; i <= m; ++i)
		for(int j = 1; j <= n; ++j)
			cnt += flip[i][j];
	return cnt;
}

void solve(){
	int res = -1;
	for(int s = 0; s < 1 << n; ++s){
		memset(flip, 0, sizeof(flip));
		for(int j = 1; j <= n; ++j)
			flip[1][j] = s >> (j - 1) & 1;
		int num = calc();
		if(num >= 0 && (res < 0 || res > num)){
			res = num;
			memcpy(ans, flip, sizeof(flip));
		}
	}
	if(res < 0)	printf("IMPOSSIBLE\n");
	else{
		for(int i = 1; i <= m; ++i)
			for(int j = 1; j <= n; ++j)
				printf("%d%c",ans[i][j], j == n ? '\n' : ' ');
	}
}

int main(){
	scanf("%d%d",&m,&n);
	for(int i = 1; i <= m; ++i)
		for(int j = 1; j <= n; ++j)
			scanf("%d",&book[i][j]);
	solve();
	return 0;
}

D.Flip Game POJ - 1753
tips:差不多的题,只是n = m = 4,数据范围大幅度缩小,且“翻成同一个面”可以都是正,也可以都是反,分两类讨论即可。
数据范围太小导致dfs也可以过,但是还是反转更优一些。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int book[10][10],flip[10][10];
int dir[5][2] = {{1,0},{-1,0},{0,0},{0,1},{0,-1}};
char c;

int getcolor(int x ,int y){
	int c = book[x][y];
	for(int k = 0; k < 5; ++k){
		int tx = x + dir[k][0], ty = y + dir[k][1];
		if(tx < 1 || ty < 1 || tx > 4 || ty > 4)	continue;
		c += flip[tx][ty];
	}
	return c % 2;
}

int calc1(){
	for(int i = 2; i <= 4; ++i)
		for(int j = 1; j <= 4; ++j)
			if(getcolor(i - 1, j))	flip[i][j] = 1;
	
	for(int j = 1; j <= 4; ++j)
		if(getcolor(4, j))	return -1;
		
	int cnt = 0;
	for(int i = 1; i <= 4; ++i)
		for(int j = 1; j <= 4; ++j)
			cnt += flip[i][j];
	return cnt;
}

int calc2(){
	for(int i = 2; i <= 4; ++i)
		for(int j = 1; j <= 4; ++j)
			if(getcolor(i - 1, j) == 0)	flip[i][j] = 1;
	
	for(int j = 1; j <= 4; ++j)
		if(getcolor(4, j) == 0)	return -1;
		
	int cnt = 0;
	for(int i = 1; i <= 4; ++i)
		for(int j = 1; j <= 4; ++j)
			cnt += flip[i][j];
	return cnt;
}

void solve(){
	int res1 = -1, res2 = -1;
	for(int s = 0; s < 1 << 4; ++s){
		memset(flip, 0, sizeof(flip));
		for(int j = 1; j <= 4; ++j)
			flip[1][j] = s >> (j - 1) & 1;
		int num1 = calc1();
		if(num1 >= 0 && (res1 == -1 || res1 > num1))	res1 = num1;
	}
	for(int s = 0; s < 1 << 4; ++s){
		memset(flip, 0, sizeof(flip));
		for(int j = 1; j <= 4; ++j)
			flip[1][j] = s >> (j - 1) & 1;
		int num2 = calc2();
		if(num2 >= 0 && (res2 == -1 || res2 > num2))	res2 = num2;
	}
	if(res1 == -1 && res2 == -1)	printf("Impossible\n");
	else if(res1 == -1)	printf("%d",res2);
	else if(res2 == -1)	printf("%d",res1);
	else	printf("%d",min(res2, res1));
} 

int main(){
	for(int i = 1; i <= 4; ++i){
		for(int j = 1; j <= 4; ++j){
			scanf("%c",&c);
			book[i][j] = (c == 'b') ? 1 : 0;		//b是1
		}
		getchar();
	}
	solve();
	return 0;	
}

E.EXTENDED LIGHTS OUT POJ - 1222
tips:同3279.多组输入,且固定m = 5, n = 6。

#include<cstdio>
#include<cstring>
using namespace std;

const int maxn = 10;
int t,n,m,book[maxn][maxn],flip[maxn][maxn],ans[maxn][maxn];
int dir[5][2] = {{1,0},{-1,0},{0,0},{0,1},{0,-1}};

int getcolor(int x, int y){
	int c = book[x][y];
	for(int k = 0; k < 5; ++k){
		int tx = x + dir[k][0], ty = y + dir[k][1];
		if(tx < 1 || ty < 1 || tx > m || ty > n)	continue;
		c += flip[tx][ty];
	}
	return c % 2;
}

int calc(){
	for(int i = 2; i <= m; ++i)
		for(int j = 1; j <= n; ++j)
			if(getcolor(i - 1, j))	flip[i][j] = 1;
	
	for(int j = 1; j <= n; ++j)
		if(getcolor(m, j))	return -1;
		
	int cnt = 0;
	for(int i = 1; i <= m; ++i)
		for(int j = 1; j <= n; ++j)
			cnt += flip[i][j];
	return cnt;
}

void solve(){
	int res = -1;
	for(int s = 0; s < 1 << n; ++s){
		memset(flip, 0, sizeof(flip));
		for(int j = 1; j <= n; ++j)
			flip[1][j] = s >> (j - 1) & 1;
		int num = calc();
		if(num >= 0 && (res < 0 || res > num)){
			res = num;
			memcpy(ans, flip, sizeof(flip));
		}
	}
	if(res < 0)	printf("IMPOSSIBLE\n");
	else{
		for(int i = 1; i <= m; ++i)
			for(int j = 1; j <= n; ++j)
				printf("%d%c",ans[i][j], j == n ? '\n' : ' ');
	}
}

int main(){
	scanf("%d",&t);
	for(int x = 1; x <= t; ++x){
		m = 5, n = 6;
		for(int i = 1; i <= m; ++i)
			for(int j = 1; j <= n; ++j)
				scanf("%d",&book[i][j]);
		printf("PUZZLE #%d\n",x);
		solve();
	}
	return 0;
}

F.The Pilots Brothers’ refrigerator POJ - 2965

tips:有点变化。以上的二维反转都是只改变自己和上下左右,这个题除了改变自己,还会改变所在行、所在列的所有格子。固定大小4*4.

一个重要的发现:如第一行第二个是+,那个只要他所在的行和列的格子全都翻转一次,就会让当前的 + 变成 - ,但是其他格子全部都不会发生变化。(证明:某一个格子所在行/列一共加起来的反转次数如果是偶数,该格子不变;奇数,该格子反转。上述操作后,整个棋盘除了该格子反转次数是奇数(4 + 3 == 7),其余位置均为偶数。可以动手算一下)
于是,发现+格子,就把他所在行列所有格子的flip++,最后答案就是所有格子的(flip%2)相加。

4*4用dfs也能过,但是用反转的思想显然比dfs快很多。

#include<cstdio>
#include<utility>
#include<vector>
using namespace std;
 
int book[5][5], flip[5][5],ans;
char c;
vector<pair<int,int> > v;
vector<pair<int,int> > :: iterator it;

void change(int x, int y){
	for(int i = 1; i <= 4; ++i)	flip[x][i]++;
	for(int i = 1; i <= 4; ++i)	flip[i][y]++;
	flip[x][y]--;
}

int main(){
	for(int i = 1; i <= 4; ++i){
		for(int j = 1; j <= 4; ++j)
			scanf("%c",&c), book[i][j] = (c == '+') ? 1 : 0;
		getchar();
	}
	for(int i = 1; i <= 4; ++i){
		for(int j = 1; j <= 4; ++j){
			if(book[i][j])	change(i, j);
		}
	}
	for(int i = 1; i <= 4; ++i)
		for(int j = 1; j <= 4; ++j){
			ans += flip[i][j] % 2;
			if(flip[i][j] % 2)	v.push_back(make_pair(i, j)); 
		}
	printf("%d\n",ans);
	for(it = v.begin(); it != v.end(); ++it)
		printf("%d %d\n",it->first, it->second);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值