hdu 3225 Flowers Placement(字典序第k小的完美匹配)

题意:用n个元素填满一个m行n列的矩阵,使得每行和每列中每个元素仅出现一次,现要再添加一行,使得矩阵仍满足上述限制,问字典序第k大的方案是什么.

解法:错误的解法是填k次,每次填最小的,若仍存在解则第k次的填充方案就是字典序第k小的,例如原有1234,第一次填2143第二次填3412不死后第二小的。

朴素的做法的dfs全排列然后验证,这样显然复杂度太高,于是考虑利用二分匹配剪枝:当递归到第i层,即第i列填充上某个元素x后,若x之后的位置和剩余的元素不能形成完美匹配则不必继续向下递归,这样验证的复杂度为O(VE);

继续简化,模拟一下过程:当i为占用了x,则原来占用x的位置j需要找到另外一个元素y,原来占用y的位置需要找到另外一个元素z。。。直到某个位置找到了i释放的元素,因此就是从j出发找一条增广路,且增广路不能经过左侧小于等于i的点(因为那些已经构成字典序了),若曾在这样一条增广路则说明i位置可以取x,否则直接剪枝,验证的复杂度为O(E)。

import java.util.Arrays;
import java.util.Scanner;

public class Flower3225 {
	int maxn = 210, maxm = 40010;
	class node {
		int be, ne;
		node(int b, int e) {
			be = b;
			ne = e;
		}
	}
	class Edmonds {
		int E[][] = new int[maxn][maxn], n, m, len;
		int link[] = new int[maxn];
		boolean vis[] = new boolean[maxn];
		void init(int n, int m) {
			this.m = m;
			this.n = n;
			len = 0;
			for (int i = 1; i <= n; i++)
				Arrays.fill(E[i], 0);
		}
		boolean find(int a,int lim) {
			if(a<=lim)
				return false;
			for (int i = 1; i <= m; i++) {
				if (E[a][i] == 0 || vis[i])
					continue;
				vis[i] = true;
				if (link[i] == -1 || find(link[i],lim)) {
					link[i] = a;
					return true;
				}
			}
			return false;
		}

		int solve() {
			Arrays.fill(link, -1);
			int ans = 0;
			for (int i = 1; i <= n; i++) {
				Arrays.fill(vis, false);
				if (find(i,0))
					ans++;
			}
			return ans;
		}
		int lk[]=new int[maxm];
		boolean check(int p,int v){
			if(link[v]==p)
				return true;
			for(int i=1;i<=m;i++)
				lk[i]=link[i];
			for(int i=1;i<=m;i++)
				if(link[i]==p)
					link[i]=-1;
			int temp=link[v];
			link[v]=p;
			Arrays.fill(vis, false);
			if(find(temp,p))
			  return true;
			for(int i=1;i<=m;i++)
				link[i]=lk[i];
			return false;
		}
	}
	Scanner scan = new Scanner(System.in);
	Edmonds hun = new Edmonds();
	int map[][] = new int[maxn][maxn], vis[] = new int[maxn],
			ans[] = new int[maxn];
	int n, m, k, cnt;
	void work() {
		hun.init(n, n);
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				hun.E[i][j] = map[i][j];
		Arrays.fill(vis, 0);
		cnt = 0;
		hun.solve();
		if (!dfs(1))
			System.out.println(-1);
	}

	boolean dfs(int d) {
		if (d > n) {
			cnt++;
			if (cnt < k)
				return false;	
			for (int i = 1; i < n; i++)
				System.out.print(ans[i] + " ");
			System.out.println(ans[n]);
			return true;
		}
		for (int i = 1; i <= n; i++)
			if (map[d][i] == 1 && vis[i] == 0 && hun.check(d, i)) {
				vis[i] = 1;
				ans[d] = i;
				if (dfs(d + 1))
					return true;
				vis[i] = 0;
			}
		return false;
	}
	void run() {
		int cas = scan.nextInt();
		for (int c = 1; c <= cas; c++) {
			System.out.print("Case #" + c + ": ");
			n = scan.nextInt();
			m = scan.nextInt();
			k = scan.nextInt();
			for (int i = 1; i <= n; i++)
				Arrays.fill(map[i], 1);
			for (int i = 1; i <= m; i++)
				for (int j = 1; j <= n; j++)
					map[j][scan.nextInt()] = 0;
			work();
		}
	}
	public static void main(String[] args) {
		new Flower3225().run();
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值