zoukankan      html  css  js  c++  java
  • 题解-亚瑟王的宫殿

    (〇)写在前面的话

    在此之前,该题已经有很多题解,但它们大多是枚举国王周围(5 imes 5)的范围(玄学贪心?),最后计算最小距离。

    虽然能(AC),但其实这种做法是不严谨的( 详见巨佬的hack数据 )

    然而将(5 imes 5)的范围扩大至(R imes C)的范围后,时间复杂度过大。那么,这道题真的无解了吗?

    蒟蒻的我用(spfa)(在此稀疏图中,显然不怕被卡)在正确的结果和正确的时间复杂度内(AC)了此题(而且过了(hack)数据)。

    (一)解题思路

    题目不再阐述。题目传送门

    这道题求的是所有骑士以及国王到某集合点的最小距离(任意骑士与国王汇合后,可以带着国王一起走,并且只算一个人的距离)。

    集合点是哪一个?不知道。不知道就枚举

    与其它题解无异,看到范围十分小的(R、C)后,毅然选择枚举集合点。

    枚举后,相当于已经知道了集合点,求骑士和国王从原所在地到该点的最小距离和。

    这个可以反过来看,也就是从集合点出发,求出骑士和国王从集合点到骑士和国王原所在地的距离的最小值。

    显然,上述两者是等价的。因此,我们可以用最短路算法直接求出最小的距离之和。这里使用(spfa)。(当然,(dijkstra)也可以,而且更稳定)没事,这题是卡不了(spfa)的。

    重点来了,敲黑板(此思路与其它题解最大的不同在这里)

    定义(dis(x, y, 0))表示集合点到点((x,y))的最小距离(最少步数,不带国王)。

    定义(dis(x, y, 1))表示集合点到点((x,y))的最小距离(最少步数,此时在点((x,y)),骑士已经带上了(或者是正在带着)国王)

    考虑如何转移。

    对于(dis(x,y,0))这个状态,可以作两种转移:

    1.不带国王,继续走自己的路。

    可以转移出八个状态(即骑士走的八个"日"字,这里不作列举),设八个状态为(dis(ex, ey, 0))。因为骑士转移到这个状态只需要一步,所以权值为(1)

    转移方程为(dis(ex, ey, 0) =min) { (dis(x, y, 0) +1) }

    2.站在原地,等国王过来,然后带上国王

    可以转移出一个状态,设国王走过来的步数为(val)((val)可以(Theta (1))求出)。转移的状态为(dis(x,y,1))

    转移方程为(dis(x,y,1)=min){(dis(x,y,0)+val)}

    对于(dis(x,y,1))这个状态,可作一种转移(毕竟骑士已经带着国王私奔去了,不可能丢下国王自己跑吧)

    1.同上,同样是可以转移出八个状态,转移方程不写了。

    (Lastquad butquad notquad least) 等等,还没完

    (spfa)跑完后,我们得到了(dis(x,y,z))数组,但题目要求的是距离和的最小值。

    简单,将最小值累加即可。等等,那么问题来了,哪位骑士带国王(???)

    也就是说,我们需要找到一个骑士,累加他的(dis(x,y,1)),同时累加其它骑士的(dis(x,y,0)),使得总和最小。

    容易证明,(dis(x,y,0)leq dis(x,y,1)) 一个人走总是比带着国王走轻松

    因此,我们只需要枚举每个((x,y)),求出(dis(x,y,1)-dis(x,y,0))的最小值,然后加上所有(dis(x,y,0))就是最小距离了。

    最差时间复杂的(Theta (R^2C^2))时间都花在枚举上了,稳稳地过,不需要卡常。

    (二)代码

    我知道大家喜欢看这个

    有注释哦。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxr = 45, maxc = 30, INF = 1e6 + 7;
    int R, C, Map[maxr][maxc], Kingx, Kingy;
    int X[8] = {-1, 1, 2, 2, 1, -1, -2, -2}, Y[8] = {2, 2, 1, -1, -2, -2, -1, 1};
    struct node{
    	int x, y;
    	bool king;//记录是否带国王
    };
    queue< node > q;
    int Abs(int x){//求绝对值 
    	if(x < 0) return -x;
    	if(x == 0) return 0;
    	if(x > 0) return x;
    }
    int spfa(int x,int y){
    	int dis[maxr][maxc][2] = {}; bool vis[maxr][maxc][2] = {};//初始化dis,vis数组(spfa常规操作) 
    	for(int i=1; i<=R; i++)//初始化dis数组,同样是常规操作 
    		for(int j=1; j<=C; j++)
    		    dis[i][j][0] = dis[i][j][1] = INF;
    	node t;
    	if(x == Kingx and y == Kingy) dis[x][y][1] = 0, t.king = true;//如果集合点的位置是国王的位置(不用考虑国王的移动),初始状态就是dis[x][y][1] 
    	else dis[x][y][0] = 0, t.king = false;//如果集合点的位置不是国王的位置,需要考虑国王的移动,初始状态是dis[x][y][0]
    	t.x = x, t.y = y;
    	q.push(t);//入队,常规操作 
    	vis[x][y][t.king] = true;//标记,常规操作 
    	while(!q.empty()){//一下均为spfa常规操作
    		int dx = q.front().x, dy = q.front().y;
    		bool flag = q.front().king;
    		q.pop();
    		for(int i=0; i<8; i++){//不进行带国王的操作 
    			int ex = dx + X[i], ey = dy + Y[i];
    			if(ex < 1 or ey < 1 or ex > R or ey > C) continue;
    			if(dis[ex][ey][flag] > dis[dx][dy][flag] + 1){
    				dis[ex][ey][flag] = dis[dx][dy][flag] + 1;
    				if(!vis[ex][ey][flag]){
    					vis[ex][ey][flag] = true;
    					node e; e.x = ex, e.y = ey, e.king = flag;
    					q.push(e);
    				}
    			}
    		}
    		if(!flag){//在(dx, dy)这个点带上国王(让国王自己走到这个点) 
    			int val = dis[dx][dy][flag] + max(Abs(dx-Kingx), Abs(dy-Kingy));
    			//算出国王走到这个点的步数,国王可以走八个方向,不是曼哈顿距离,而是max(Abs(dx-Kingx), Abs(dy-Kingy),自行画图理解 
    			if(dis[dx][dy][!flag] > val){
    				dis[dx][dy][!flag] = val;
    				if(!vis[dx][dy][!flag]){
    					vis[dx][dy][!flag] = true;
    					node e; e.x = dx, e.y = dy, e.king = !flag;
    					q.push(e);
    				}
    			}
    		}
    		vis[dx][dy][flag] = false;
    	}
    	int minu = INF, cnt = 0;//最短路跑完后,算出最短的总距离
    	//容易证明,骑士所在的点的状态dis[x][y][1]>=dis[x][y][0],要使总距离最小,只需要找最小的(dis[x][y][1]-dis[x][y][0]),最后加上所有骑士的距离dis[x][y][0]即可 
    	for(int i=1; i<=R; i++)
    	    for(int j=1; j<=C; j++)
    	    	if(Map[i][j] == 1) minu = min(minu, dis[i][j][1] - dis[i][j][0]), cnt += dis[i][j][0];
    	return cnt+minu;
    }
    int main(){
    	cin>>C>>R;//写到最后发现R,C打反了,不想改了,将就着看吧 
    	char kingx;
    	cin>>kingx>>Kingy;//读入 
    	Kingx = kingx-'A'+1;//Kingx储存国王的横坐标,Kingy储存国王的纵坐标 
    	char knightx; int knighty;
    	while(cin>>knightx>>knighty){//读入 
    		Map[knightx-'A'+1][knighty] = 1;//邻接矩阵标记国王的位置 
    	}
    	int ans = INF;//初始化 
    	for(int i=1; i<=R; i++) 
    		for(int j=1; j<=C; j++)
    			ans = min(ans, spfa(i, j));//枚举集合点,spfa求最短距离 
    	if(ans == INF) printf("0");
    	else printf("%d", ans);
    	return 0;
    }
    
  • 相关阅读:
    Windows下如何检测用户修改了系统时间并且把系统时间改回来
    洛谷 1220 关路灯
    洛谷 2279 [HNOI2003]消防局的设立
    洛谷 1498 南蛮图腾
    bzoj 1036 [ZJOI2008]树的统计Count 树链剖分模板
    codevs 1021 玛丽卡 SPFA
    codevs 1077 多源最短路 flyod
    Vijos P1133 装箱问题 01背包
    codevs 1069 关押罪犯 并查集
    codevs 1073 家族 并查集
  • 原文地址:https://www.cnblogs.com/GDOI2018/p/13492374.html
Copyright © 2011-2022 走看看