题目大意:
有两个序列a,b,a序列和b序列的每个元素 , (ai + bi) % n 的值可以构成ci,题目希望通过调整b序列的顺序,来使得C序列字典序最小。
解法:先扫一遍b序列,可以得到数值的个数,对于ai的每个元素,从左往右起,若数值:(n - a[i]) % n 的个数还有剩,则bi这个位置换成这个数值,否则,贪心的往上找,找(n - a[i] + 1) % n 是否存在,有就放,没有就继续往下,因为元素个数都是n,循环一圈每个数字总能找到要放的数字。但找数字的过程是一个可优化的过程,中间如果空了很大一段我们可以跳着找,这种跳着找的优化就叫并查集。
具体来说,每个数值有个指针数组,若该数值存在(b序列中有这个数值),则指针数组指向该数值,否则指针数组指向下一个数字,这样就搞成了一个链表,每次我们沿着链表去找就可以。而并查集的路径压缩,可以优化这个链表,因为链表正是并查集退化的情况,这也是并查集优化的一种常见形式(终于补上了对并查集的理解)。
至于multiset,其实就是暴力的做法,每用掉一个数字,就删掉一个数字,因为找不到数字的时候我们是递增的往上找,所以可以直接在multiset上二分查找我们可以放的最优的数值,而如果在右边区间查找不到,那么就在左边区间找个最小的,看别人都是跑400+ ms。并查集优化只跑140ms。(并查集优化的复杂度优于log(n),是必备的优化技能啊)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
int vis[maxn];
int a[maxn],b[maxn],c[maxn];
int n;
int p[maxn];
int find(int x) {
int t = x;
while(t != p[t])
t = p[t];
int fa;
while(x != t) {
fa = p[x];
p[x] = t;
x = fa;
}
return t;
}
int main() {
scanf("%d",&n);
for(int i = 1; i <= n; i++) {
scanf("%d",&a[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d",&b[i]);
vis[b[i]]++;
}
for(int i = 0; i < n; i++) {
p[i] = i;
}
for(int i = 0; i < n; i++)
p[i] = vis[i] ? i : (i + 1) % n;
for(int i = 1; i <= n; i++) {
int v = a[i];
int u = (n - a[i]) % n;
int num = find(u);
c[i] = (a[i] + num) % n;
vis[num]--;
if(!vis[num]) p[num] = (num + 1) % n;
}
for(int i = 1; i <= n; i++) {
printf("%d ",c[i]);
}
return 0;
}