蓝桥杯 The Great Wall Game Java 二分图 KM算法

问题描述

小华和小沈发明了一个简单的棋盘游戏,他们称之为“长城游戏”。这个游戏需要一个n*n的网格和n颗石子。这些石子随机地放在网格的方格之中,一个格子中最多放一颗石子。每一次移动,可以将任意一颗石子移动到上下左右相邻的空方格之中。游戏的目标是用最少的移动步数,使得n颗石子构成“一堵墙”——排成一条水平、竖直或斜的直线。
  现在的问题是,小华和小沈不知道对于一个给定的初始棋盘,达到目标需要移动的最小步数。他们想要你写一个程序能够实现对于任意一个给定的初始状态,求出将所有石子排成一条直线所需要的最小步数。

输入格式

输入由多组数据组成。每组数据第一行包含一个整数n,1<=n<=15。接下来n行,每行包含两个数分别表示每颗石子所在的行和列。行和列的编号如上图。输入数据最后一行包含一个数0表示结束。

输出格式

对于每一组数据,输出数据的编号和将所有n颗石子排成一条直线所需的最小移动步数。按照样例中的输出格式输出。每组数据之后输出一个空行。

样例输入

5
1 2 2 4 3 4 5 1 5 3
2
1 1 1 2
3
3 1 1 2 2 2
0

样例输出

Board 1: 6 moves required.

Board 2: 0 moves required.

Board 3: 1 moves required.

题解

二分图:列举横竖斜线所有情况,把直线上的点当作y集,给你的点当作x集,x,y组成二分图,边的权值为x点到y点的曼哈顿距离。
求每种情况的最优匹配(KM 算法 )

package ADV;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

/**
 * The Great Wall Game
 * http://lx.lanqiao.cn/problem.page?gpid=T514
 * AC
 */
public class ADV273 {
	static int turn = 0;//每组数据轮次
	static String[] s;
	static int[] posX;//石子行号
	static int[] posY;//石子列号
	static int[][] edge;
	static int[] Lx,Ly;//顶标
	static boolean[] sx,sy; //记录寻找增广路时点集x,y里的点是否搜索过
	static int[] match; //match[i]记录y[i]的匹配点编号
	static int n;
	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		while(true) {
			turn++;
			s = in.readLine().split(" ");
			n = Integer.valueOf(s[0]);
			if(n==0)
				break;
			
			s = in.readLine().split(" ");
			init();
			int ans = 0x3f3f3f3f;
			//枚举一列直线
			for (int i = 1; i <= n; i++) {//第i列
				for (int j = 0; j < n; j++) {//对每个石子
					for (int k = 0; k < n; k++) {//对直线上的y集
						add_Edge(j, k, -dis(posX[j],posY[j],i,k+1));
					}
				}
				KM();
				int sum = 0;
				for(int u= 0;u < n;u++)
	                sum += -edge[match[u]][u];
	            ans = Math.min(ans,sum);
			}
			//枚举一行直线
			for (int i = 1; i <= n; i++) {//第i行
				for (int j = 0; j < n; j++) {//对每个石子
					for (int k = 0; k < n; k++) {//对直线上的y集
						add_Edge(j, k, -dis(posX[j],posY[j],k+1,i));
					}
				}
				KM();
				int sum = 0;
				for(int u= 0;u < n;u++)
	                sum += -edge[match[u]][u];
	            ans = Math.min(ans,sum);
			}
			//枚举2条斜线
			
			for (int j = 0; j < n; j++) {//对每个石子
				for (int k = 0; k < n; k++) {//对直线上的y集
					add_Edge(j, k, -dis(posX[j],posY[j],k+1,n-k));
				}
			}
			KM();
			int sum = 0;
			for(int u= 0;u < n;u++)
                sum += -edge[match[u]][u];
            ans = Math.min(ans,sum);
			
            for (int j = 0; j < n; j++) {//对每个石子
				for (int k = 0; k < n; k++) {//对直线上的y集
					add_Edge(j, k, -dis(posX[j],posY[j],k+1,k+1));
				}
			}
			KM();
			sum = 0;
			for(int u= 0;u < n;u++)
                sum += -edge[match[u]][u];
            ans = Math.min(ans,sum);
            
            System.out.println("Board "+turn+": "+ans+" moves required.\n");

		}
	}
	static void add_Edge(int u, int v, int val) {
		edge[u][v] = val;
	}
	static void KM() {
		Arrays.fill(match, -1);
		for(int i = 0; i < n; i++) {//顶标
			Lx[i] = Ly[i] = 0;
			for(int j = 0; j < n; j++)
				Lx[i] = Math.max(Lx[i], edge[i][j]);
		}
		//不断修改顶标,直到找到完备匹配或完美匹配
		for(int i = 0; i < n; i++) {//为x里的每一个点找匹配
		  while(true) {
			  Arrays.fill(sx, false);
			  Arrays.fill(sy, false);
			  if(match(i)) //x[i]在相等子图找到了匹配,继续为下一个点找匹配
				  break; 
			  else 
				  update();	//如果在相等子图里没有找到匹配,就修改顶标,直到找到匹配为止

		  }
		}
	}

	static boolean match(int i){ //给x[u]找匹配,匈牙利匹配
	  sx[i] = true;
	  for(int j = 0; j < n; j++) 
		  if (Lx[i]+Ly[j] == edge[i][j] && !sy[j]){
			  sy[j] = true;
			  if (match[j]==-1 || match(match[j])){//如果y集合j没有匹配或者为j的匹配match[j]换了一个对象
				  match[j] = i;
				  return true;
			  }
	  }
	  return false;
	}
	static void update(){
		//匹配失败时首先找到修改顶标时的增量a=min(lx[i] + ly [i] - weight[i][j],a); a为最小的顶标改变量
		//lx[i]为搜索过的x点,ly[i]是未搜索过的y点
		//因为如果sx[i]与sy[j]都为真,则必然符合lx[i] + ly [j] =weight[i][j],a则为0没有改变
		//所以只需要修改找的过程中搜索过的点,
		int a = 0x3f3f3f3f;
		for(int i = 0; i < n; i++) 
			if(sx[i])
			for(int j = 0; j < n; j++) 
				if(!sy[j])
				a = Math.min(a, Lx[i]+Ly[j] - edge[i][j]);
		//找到增量后修改顶标
		//将lx[i]减inc,ly[j]加inc不会改变等式
		//但是如果sx[i]与sy[j]只有一个为真,lx[i] + ly [j] !=weight[i][j]

		for(int i = 0; i < n; i++) {
			if(sx[i]) 
				Lx[i] -= a;
		    if(sy[i]) 
		    	Ly[i] += a;
		}
	}

	static void init() {
		posX = new int[n];
		posY = new int[n];
		for (int i = 0; i < s.length; i+=2) {
			posX[i/2] = Integer.valueOf(s[i]);
			posY[i/2] = Integer.valueOf(s[i+1]);
		}
		edge = new int[n][n];
		Lx = new int[n];
		Ly = new int[n];
		sx = new boolean[n];
		sy = new boolean[n];
		match = new int[n];
	}
	static int dis(int x1, int y1, int x2, int y2) {
		return Math.abs(x1 - x2) + Math.abs(y1 - y2);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CTF(Capture The Flag)是一种网络安全竞赛,参赛者通过解决一系列的题目,获取隐藏在其中的flag,以此来展示其网络安全攻防技术。而"the great wall"指的是中国长城,因此"CTF the great wall"可以理解为以中国长城为主题的CTF比赛。 "CTF the great wall"可将中国长城作为背景,提供一系列与其相关的题目。比赛的参与者可以通过解决这些题目,找到隐藏的flag,从而实现攻防技术的展示。 在CTF the great wall比赛中,可能会包括以下类型的题目: 1. 密码学:设置一些加密算法和密码,参赛者需要解密以找到flag。 2. 网络通信:模拟网络通信中的漏洞和攻击场景,参赛者需要找到并利用这些漏洞,获取flag。 3. 操作系统和二进制:提供一些二进制文件或者脚本,参赛者需要分析其中的漏洞,通过调试和逆向工程找到flag。 4. Web漏洞:提供一些网站,参赛者需要通过测试和分析发现其中存在的安全漏洞,从而找到flag。 通过这些题目,参赛者可以学习和展示各种网络安全攻防技术,例如密码学、网络漏洞挖掘、二进制漏洞挖掘和Web漏洞挖掘等,同时也能提高对中国长城的了解和认识。 总而言之,"CTF the great wall"是一种用中国长城作为主题的网络安全竞赛,通过解决一系列与长城相关的题目,参赛者能够在实践中学习和展示网络安全攻防技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值