P2346 四子连棋 题解

CSDN同步

原题链接

2020.4.1

在这里立个 ( ext{flag}):一周内 ( exttt{AC}) 不了这道题目,我就 倒 ! 立 ! 洗 ! 头 !

本人还没 ( exttt{AC}),不过呢,谔谔,先整理下思路吧。

简要题意:

给定一个棋局,双方轮流将自己的棋子走到空位(任意方先走),问使得“四子连棋”(即四个子在一条直线上,斜线也可以)的最小步数。

显然,(4 imes 4) 的棋盘只要不出问题就能 ( ext{AC}).(但我。。。)

本题需要优化搜索!(其实不然)

假设你用纯属的 ( exttt{bfs}),旁边开着哈希,你完全 有可能将所有状态都遍历一遍

而所有状态的种数是(根据组合计算):

[frac{16!}{7! imes 7! imes 2!} = 411840 ]

然后,你映射到哈希还需要 (4^2 = 16) 的时间,然后还要每次判断答案再用 (4^2 = 16) 的时间,其它的不说,时间复杂度达到了:

[O(4 imes 10^5 imes 16^2) > O(10^8) ]

再快的评测机,你广搜的常数也不会小的,然后妥妥的 ( ext{T}) 掉了。。

你说:好,我用双向 ( ext{bfs}).

没错确实可以,但这边不讲 究竟是懒,还是博主不会,大家细细品!

那么 ( ext{A*}) 是什么呢?(这么神秘)

其实,( ext{A*}) 的搜索可以保证每次的状态越来越优(至少也不会变劣)。

假设当前状态为 (x),转移状态为 (y)对状态的咕值函数为 (f)(没错就是这个咕,咕咕咕的咕),则必须满足:

[f_x leq f_y ]

此时才可以进行转移,否则 ( ext{A*}) 就直接把它结束。

为什么呢?

你想,如果你转移一步状态反而变劣了,那肯定还不如不转,对不对?

那么,这个 咕值函数 在本题中是什么呢?

就是 最大的连续棋子

也就是说,如果你走了一步,原来连续的几个棋子被你破坏导致答案变劣,那这步就可以去世了。

那你说,如果我在破坏一个的同时又创造了新的一个呢?那不会影响,因为你新的一个如果还是较劣也会被剔除,否则这步就是可以的。

至少,( ext{A*}) 满足状态越来越优(至少不会变劣),那么就非常的好。(好!好!好!)

这样可以认为是一个 可行性剪枝 但是创造者为了提高咕值就把它列成算法怎么地

但是,在本题中,这个优化没有什么用处。

因为一个状态至少有 (2) 个连续的棋子(反证可知),所以如果原棋盘的咕值是 (2),那么还是会遍历的。

不过如果是 (3) 的话那瞬间时间复杂度就降到地下了。。(不过随机数据还没这个概率)

时间复杂度:(O( exttt{wys})).(玄学,因为完全不会遍历所有状态)

实际得分:(80pts).(本人不知道哪里写炸了,如果有误可以在讨论区提出!)

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

struct node{
	char p[5][5];
	int step; char tt; //tt 表示上一局的走棋方
};

char a[5][5];
map<string,int> h;
queue<node> q;

const int dx[4]={1,-1,0,0};
const int dy[4]={0,0,1,-1};

inline node mp(char a[5][5],int x,char tt) {
	node t; t.tt=tt; for(int i=1;i<=4;i++) 
	for(int j=1;j<=4;j++) t.p[i][j]=a[i][j];
	t.step=x; return t;
} //将数据整装成结构体

inline string turn(char a[5][5]) {
	string st="";
	for(int i=1;i<=4;i++)
	for(int j=1;j<=4;j++) st+=a[i][j];
	return st;
} //压缩成字符串(其实3进制状压也行)

inline pair<int,int> get(char a[5][5],int x,int y) {
	for(int i=1;i<=4;i++) for(int j=1;j<=4;j++)
		if(a[i][j]=='O' && (i!=x || j!=y))
			return make_pair(i,j);
} //寻找不在 (x,y) 位置的 O ,第一次找到后可以顺便找第二次

inline int f(char a[5][5]) {
	int maxi=1; for(int i=1;i<=4;i++) {
		if(a[i][1]==a[i][2] && a[i][2]==a[i][3] && a[i][3]==a[i][4]) return 4;
		if(a[i][2]==a[i][3] && (a[i][1]==a[i][2] || a[i][3]==a[i][4])) maxi=max(maxi,3);
		else if(a[i][1]==a[i][2] || a[i][2]==a[i][3] || a[i][3]==a[i][4]) maxi=max(maxi,2); //横着
	} for(int i=1;i<=4;i++) {
		if(a[1][i]==a[2][i] && a[2][i]==a[3][i] && a[3][i]==a[4][i]) return 4;
		if(a[2][i]==a[3][i] && (a[1][i]==a[2][i] || a[3][i]==a[4][i])) maxi=max(maxi,3);
		else if(a[1][i]==a[2][i] || a[2][i]==a[3][i] || a[3][i]==a[4][i]) maxi=max(maxi,2); //竖着
	} bool l[5][5]; memset(l,0,sizeof(l));
	for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) {
		if(l[i][j]) continue;
		int x=i,y=j;
		while(x<=3 && y<=3 && a[x+1][y+1]==a[x][y]) l[++x][++y]=1;
		maxi=max(maxi,x-i+1); //往右下角走
	} memset(l,0,sizeof(l));
	for(int i=1;i<=3;i++) for(int j=2;j<=4;j++) {
		if(l[i][j]) continue;
		int x=i,y=j;
		while(x<=3 && y>=2 && a[x+1][y-1]==a[x][y]) l[++x][--y]=1;
		maxi=max(maxi,x-i+1); //往左下角走
	} return maxi;
} //咕值函数

/*inline void print(char a[5][5],int step) {
	putchar('
');
		for(int i=1;i<=4;i++) {
			for(int j=1;j<=4;j++) putchar(a[i][j]);
	 		putchar('
');
	} printf("%d
",step);
}*/ //调试的代码

inline void bfs() {
	q.push(mp(a,0,'O')); h[turn(a)]=1;
	while(!q.empty()) {
		node t=q.front(); char now[5][5]; int step;
		for(int i=1;i<=4;i++) for(int j=1;j<=4;j++)
			now[i][j]=t.p[i][j]; step=t.step; //取出队首
		q.pop();  //print(now,step); //出队
		pair<int,int> pr=get(now,0,0);
		pair<int,int> pr2=get(now,pr.first,pr.second); //得到两个位置
//		printf("%d %d %d %d
",pr.first,pr.second,pr2.first,pr2.second);
		for(int i=0;i<4;i++) {
			int x=dx[i]+pr.first,y=dy[i]+pr.second;
			if(x<1 || y<1 || x>4 || y>4 /*|| now[x][y]==t.tt*/ || now[x][y]=='O' || now[x][y]==t.tt) continue;
                        //不是上一局的那一方,并且不是两个空位转化
			swap(now[x][y],now[pr.first][pr.second]);
			int u=f(now),v=f(t.p);
			if(u<v || h[turn(now)]) { //变劣 或 已出现
				swap(now[x][y],now[pr.first][pr.second]); 
				continue; }
			h[turn(now)]=1;	q.push(mp(now,step+1,a[x][y])); //入队,标记
			if(u==4) { //达到目标状态
//				print(now,step+1);
				printf("%d
",step+1);
				exit(0);
			} swap(now[x][y],now[pr.first][pr.second]); //记得再换回去
		} pr=pr2; //对第二个位置同样做一遍
		for(int i=0;i<4;i++) {
			int x=dx[i]+pr.first,y=dy[i]+pr.second;
			if(x<1 || y<1 || x>4 || y>4 /*|| now[x][y]==t.tt*/ || now[x][y]=='O' || now[x][y]==t.tt) continue;
			swap(now[x][y],now[pr.first][pr.second]);
			int u=f(now),v=f(t.p);
			if(u<v || h[turn(now)]) {
				swap(now[x][y],now[pr.first][pr.second]);
				continue; }
			h[turn(now)]=1; q.push(mp(now,step+1,a[x][y]));
			if(u==4) {
//				print(now,step+1);
				printf("%d
",step+1);
				exit(0);
			} swap(now[x][y],now[pr.first][pr.second]);
		}
	}
}

int main(){
	for(int i=1;i<=4;i++)
	for(int j=1;j<=4;j++) cin>>a[i][j];
	if(f(a)==4) puts("0"); //特判一开始就是目标状态
	else bfs();
	return 0;
}

原文地址:https://www.cnblogs.com/bifanwen/p/12613378.html