@hdu - 6687@ Rikka with Stable Marriage


@description@

给定一个稳定婚姻匹配问题,其中第 i 个男生与第 j 个女生之间的喜爱度为 ai xor bj。

现在需要你求出所有稳定婚姻匹配中 ∑(ai xor bj) 的最大值。

注:关于什么是稳定婚姻匹配。
第 i 个男生对于第 j 个女生有一个喜爱度 Aij,第 j 个女生对第 i 个男生有一个喜爱度 Bij(在本题中 Aij = Bij)。
一个稳定婚姻匹配应该满足:如果将 x 与 y 匹配,u 与 v 匹配,则不应该出现 x 对 v 的喜爱度 > x 对当前的 y 的喜爱度,且 v 对 x 的喜爱度 > v 对当前的 u 的喜爱度。

Input
第一行一个整数 T 表示数据组数。
对于每组数据,开头一行包含一个整数 n (1≤n≤10^5)。
第二行包含 n 个整数 a1, a2, ..., an (1≤ai≤10^9)。
第三行包含 n 个整数 b1, b2, ..., bn (1≤bi≤10^9)。
保证所有的 ai 互不相等,保证所有的 bi 互不相等。

Output
对于每组数据,输出一个整数表示 ∑(ai xor bj) 的最大值。如果无解输出 -1。

Sample Input
2
4
1 2 3 4
1 2 3 4
5
10 20 30 40 50
15 25 35 45 55
Sample Output
20
289

@solution@

稳定婚姻问题挺有意思的 www(虽然这道题基本只需要用到它的定义)。
稳定婚姻匹配不可能无解,所以无解输出 -1 是假的。

假如 x 在 y 心中是最棒的,y 在 x 心中是最棒的,则 x 与 y 一定可以存在于稳定婚姻匹配中。
且由于 ai, bi 互不相同,ax xor by 与其他的 ax xor bv, au xor ay 也一定不相同,且一定比它们大。
由此,如果 x, y 与其他人 u, v 匹配,一定会出现不稳定的情况。则可以发现 x 与 y 存在于所有稳定婚姻匹配中。
我们不断地找这样一对 (x, y),并把它们同时删去。

怎么找呢?假如 x 与 y 的喜爱度 ax xor by 是全局最大的,则他们一定是互相认为最棒的。
我们就不断地找 ax xor by 最大值及其对应的 x 与 y,然后删去 x 与 y 递归求解。
可以发现一定有一个时刻所有人都删完了,而且中途不会出现多种选择。故这个题中稳定婚姻匹配是唯一的。

那么问题就在于怎么不断地找 ax xor by 的最大值。
两个值的 xor 最大值可以联想到 trie 树。两棵 trie 树找结点 xor 最大值,或许我们可以进行 trie 的 “合并”。

对于两棵 trie 树 T1, T2,首先最高位尽量不同才能 xor 出来最大。
所以首先应该尝试递归 T1->child[0] 与 T2->child[1],T1->child[1] 与 T2->child[0] 看能否找到最大值。
如果有一棵树为空了,则匹配无法继续,直接停止递归并返回就好了。

此时注意到这两次递归并不会互相影响,且这两次递归找出来的一定比 0, 0 匹配、1, 1 匹配大,所以可以同时进行。
用不着像我们一开始的算法那样先找全局 xor 最大值,再找全局次大值……
过后还要尝试递归 T1->child[0] 与 T2->child[0],T1->child[1] 与 T2->child[1],将匹配剩余的继续拿去匹配。

复杂度?因为只有两棵树同时不为空时才会继续,那么复杂度 <= 将 n 对匹配插入 trie 树中的总复杂度。
即复杂度为 O(nlogA),A 为值域。

@accepted code@

#include <cstdio>
typedef long long ll;
const int MAXN = 100000;
int ch[2][2][35*MAXN + 5], cnt[2][35*MAXN + 5], ncnt[2];
int newnode(int t) {
	int p = (++ncnt[t]);
	ch[t][0][p] = ch[t][1][p] = cnt[t][p] = 0;
	return p;
}
void insert(int rt, int dep, const int &x, const int &t) {
	cnt[t][rt]++;
	if( dep == -1 ) return ;
	int dir = (x >> dep) & 1;
	if( !ch[t][dir][rt] )
		ch[t][dir][rt] = newnode(t);
	insert(ch[t][dir][rt], dep - 1, x, t);
}
ll ans; int n;
void cal(int rt1, int rt2, int dep, int x) {
	if( !cnt[0][rt1] || !cnt[1][rt2] )
		return ;
	if( dep == -1 ) {
		ans += x, cnt[0][rt1]--, cnt[1][rt2]--;
		return ;
	}
	cal(ch[0][0][rt1], ch[1][1][rt2], dep - 1, x|(1<<dep));
	cal(ch[0][1][rt1], ch[1][0][rt2], dep - 1, x|(1<<dep));
	cal(ch[0][0][rt1], ch[1][0][rt2], dep - 1, x);
	cal(ch[0][1][rt1], ch[1][1][rt2], dep - 1, x);
	cnt[0][rt1] = cnt[0][ch[0][0][rt1]] + cnt[0][ch[0][1][rt1]];
	cnt[1][rt2] = cnt[1][ch[1][0][rt2]] + cnt[1][ch[1][1][rt2]];
}
void solve() {
	scanf("%d", &n);
	ncnt[0] = 0, newnode(0);
	for(int i=1;i<=n;i++) {
		int x; scanf("%d", &x);
		insert(1, 30, x, 0);
	}
	ncnt[1] = 0, newnode(1);
	for(int i=1;i<=n;i++) {
		int x; scanf("%d", &x);
		insert(1, 30, x, 1);
	}
	ans = 0; cal(1, 1, 30, 0);
	printf("%lld\n", ans);
}
int main() {
	int T; scanf("%d", &T);
	while( T-- ) solve();
}

@details@

话说这种新的两棵 trie 树搞合并方法。。。感觉还是比较新颖的?
感觉以前都没有见过,又感觉好像很经典。。。

posted @ 2019-11-13 08:36  Tiw_Air_OAO  阅读(214)  评论(0编辑  收藏  举报