牛客挑战赛53E题解 & 带花树学习笔记

登录—专业IT笔试面试备考平台_牛客网

显然就是一个点能和两个坐标距离它(2,0)或(2,1)的点匹配,求最大匹配数。

之前一直以为一般图最大匹配是np的,一直在分析这题有没有什么格点图的特殊性质,结果比赛结束才知道一般图最大匹配有一个叫带花树的算法(很久之前听过但压根没去学这种冷门算法...)

之前一篇贴了带花树板子以及大佬的链接,这里稍微说下个人理解。

带花树思想和匈牙利算法一致,仍然是不断地找增广路,问题在于增广路中会出现奇数个点组成地环,这个东西定义为“花”,即我们把里面的点类似缩连通分量一样缩在一起,可以发现我们可以通过调整花内匹配边的情况,能使得花中匹配情况为恰好一个点(可以是花中任意一点)未匹配,其余点都匹配上。缩花完成后(可能花套花)之后变成一棵树,直接类似匈牙利算法找增广路,找到后把路径上的花依次打开并赋匹配边情况。

不难理解,但是感觉自己去实现要头秃,所以对着网上大佬写的模仿了个板子......(求lca大概是找到花中第一个被搜索到的点(也称花托),并查集维护缩在一个花里的点)

#include <bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v, x) for(auto v:x)
#define VI vector<int>
#define VLL vector<ll>
//define double long double
#define all(x) (x).begin(),(x).end()
using namespace std;
const double eps = 1e-10;//1e-12
const int N = 610;
const ll mod = 998244353;//1e9 + 7;

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

int tot, m, val[30][30], ans[30][30];
VI adj[N];
int match[N], fa[N], vis[N], pre[N];
queue<int>que;

int find(int x)
{return fa[x] == x ? x : fa[x] = find(fa[x]);}

int calc_lca(int x, int y)
{
	static int buk[N];
	static int nw;
	++nw;
	while(1)
	{
		if(x)
		{
			x = find(x);
			if(buk[x] == nw)
				return x;
			buk[x] = nw;
			x = pre[match[x]];
		}
		swap(x, y);
	}
}

void shrink(int x, int y, int lca)
{
	while(find(x) != lca)
	{
		pre[x] = y, y = match[x];
		if(vis[y] == 2)
		{
			vis[y] = 1;
			que.push(y);
		}
		if(find(x) == x)
			fa[x] = lca;
		if(find(y) == y)
			fa[y] = lca;
		x = pre[y];
	}
}

bool aug(int s)
{
	for(int i = 1; i <= tot; i++)
		fa[i] = i;
	memset(vis, 0, sizeof vis);
	memset(pre, 0, sizeof pre);
	while(!que.empty())
		que.pop();
	vis[s] = 1;
	que.push(s);
	while(!que.empty())
	{
		int nw = que.front();
		que.pop();
		trav(nxt, adj[nw])
		{
			if(find(nw) == find(nxt) || vis[nxt] == 2)
				continue;
			if(!vis[nxt])
			{
				vis[nxt] = 2;
				pre[nxt] = nw;
				if(!match[nxt])
				{
					for(int x = nxt, y; x; x = y)
					{
						y = match[pre[x]];
						match[x] = pre[x];
						match[pre[x]] = x;
					}
					return 1;
				}
				vis[match[nxt]] = 1, que.push(match[nxt]);
			}
			else
			{
				int lca = calc_lca(nw, nxt);
				shrink(nw, nxt, lca);
				shrink(nxt, nw, lca);
			}
		}
	}
	return 0;
}

void sol()
{
	int n;
	cin >> n >> m;
	int nx, ny;
	cin >> nx >> ny;
    tot = 0;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= m; j++)
		{
			val[i][j] = ++tot;
		}
	}
	for(int i = 1; i <= tot; i++)
		adj[i].clear(), match[i] = 0;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= m; j++)
		{
			if(i == nx && j == ny)
				continue;
			for(int d = 0; d < 12; d++)
			{
				int ni = i + dx[d];
				int nj = j + dy[d];
				if(ni == nx && nj == ny)
					continue;
				//cerr << i << ' ' << j << ' ' << ni << ' ' << nj << '\n';
				if(ni >= 1 && ni <= n && nj >= 1 && nj <= m)
					adj[val[i][j]].pb(val[ni][nj]);
			}
		}
	}
	//int ans = 0;
	for(int i = 1; i <= tot; i++)
	{
		if(!match[i])
			aug(i);
	}
	//cout << ans << '\n';
	int num = 0;
	for(int i = 1; i <= n; i++)
	{	
		for(int j = 1; j <= m; j++)
		{
			int v = match[(i - 1) * m + j];
			if(v == 0)
				ans[i][j] = -1;
			else
			{
				if(v > (i - 1) * m + j)
				{
					++num;
					ans[i][j] = num;
					ans[(v - 1) / m + 1][(v - 1) % m + 1] = num;
				}
			}
		}
	}
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= m; j++)
		{
			cout << ans[i][j] << ' ';
		}
		cout << '\n';
	}
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int tt;
	cin >> tt;
	while(tt--)
	{
		sol();
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值