@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 树搞合并方法。。。感觉还是比较新颖的?
感觉以前都没有见过,又感觉好像很经典。。。