zoukankan      html  css  js  c++  java
  • 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;
    }
    
    
  • 相关阅读:
    考试心得 模拟18
    模拟17 题解
    模拟16 题解
    考试心得 模拟17
    模拟15 题解(waiting)
    BZOJ2653 middle 【主席树】【二分】*
    BZOJ3932 CQOI2015 任务查询系统 【主席树】
    与或 【线段树】 *
    BZOJ1014 JSOI2008 火星人prefix 【非旋转Treap】*
    网络流--最大权闭合子图 *
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12613378.html
Copyright © 2011-2022 走看看