Codeforces Round 773 Div.1 D. Two Arrays
本章内容
本章为Codeforces Round #773 Div.1 D. Two Arrays的题解。
题目内容
给定 \(n\) 个长度为 \(m\) 的数组 \(a_1,a_2,...,a_n\)。第 \(i\) 个数组有一个附加权值 \(j\)。
定义 \(val(i,j)=w_i+w_j\),对于所有的满足数组
\(a_i,a_j\) 内的数字两两不同的 \(i,j\),求 \(val(i,j)\)
的最小值。如果不存在两个数组满足它们内的数字两两不同,输出
-1
。
\(n≤10^5,\quad m≤5,\quad 1≤a_{i,j},\enspace
w_i≤10^9\)。
解题思路
常规想法
我们很容易想到,通过枚举\(i\),\(j\)来计算,用一个\(map\)来存储第\(i\)行的数,然后判断第\(j\)行的数与其是否有重复,没有重复则计算\(w_i+w_j\),但是由于题目数据范围\(n\leq 10^5\),所以\(O(n^2)\)的时间复杂度太高,会超时。
本题解法
因此,我们需要考虑一个能够很方便判断两行数是否有交集的方法,观察数据范围\(m\leq 5\),可以考虑\(2^m\)的构造方法。
关于判断序列{a}和{b}有无相同元素的判断方法:记一个计数器\(cnt\),对于每一个{b}的子集,首先判断它在{a}是否出现,如果出现,则分为两种情况。如果该子集的元素个数是奇数,就将\(cnt\)加上它在{a}中的出现次数,如果元素个数是偶数,则将\(cnt\)减去它在{a}中的出现个数。如果最后\(cnt=1\)则说明至少有一个元素相同,否则它们就没有相同元素。
对于上述的方法,不只适用于单独判断{a}和{b}序列,也适用于判断{a}和多个序列是否有相同元素,可以想象成{a}与多个序列的每个序列一个个判断,如果与一个序列有相同的,则 \(cnt\)++,如果最后 \(cnt\) 等于多个序列的个数,则说明全都有相同元素。
对于 n 行序列,首先我们按 \(w_i\) 从小到大将其排序,设有左端点 \(l\) 序列和右端点 \(r\) 序列,对于左端点 \(l\) ,我们希望找到符合条件的最小 \(r\)。如果我们从左到右依次枚举 \(l\),然后找到与 \(l\) 序列没有相同元素的最小 \(r\) 序列,这为当前的一组解。如果要找到比\((l, r)\)更优的解,则只有可能从 \((l,r)\) 的区间内再继续寻找,因为 \(l\) 是枚举的,接下来的 \(l\) 只会比当前的大,而为了达到总和 \(w_i+w_j\) 更小的目的,\(r\) 只能往左移动。所以相当于双指针,\(l\) 从左往右,\(r\) 从右往左,直至两者相遇。
时间复杂度为\(O(n \log(n2^m))\)
流程
- 首先,将所有的序列的所有子集放进\(map\)
- 设置双指针,左端为 \(l=0\),右端为 \(r=n-1\)
- 从左到右枚举 \(l\),在 \(map\) 中判断 \((l, r]\) 区间内是否存在符合要求的序列,如果个数小于 \(r-l\) ,则说明存在这样的序列,逐渐将 \(r\) 向左移动,找到满足要求的最小 \(r\),移动过程中不在 \((l, r]\) 区间的序列子集要从 \(map\) 中删除
- 判断ans和当前的dat[l].w + dat[r].w大小,重复步骤3、4
代码
1 |
|
附
- __builtin_popcount(unsigned int n) 该函数时判断n的二进制中有多少个1
1 | int n = 15; //二进制为1111 |
- __builtin_parity(unsigned int n) 该函数是判断n的二进制中1的个数的奇偶性
1 | int n = 15;//二进制为1111 |
- __builtin_ffs(unsigned int n) 该函数判断n的二进制末尾最后一个1的位置,从1开始
1 | int n = 1;//1 |
- __builtin_ctz(unsigned int n) 该函数判断n的二进制末尾后面0的个数,当n为0时,和n的类型有关
1 | int n = 1;//1 |
- __builtin_clz (unsigned int x) 返回前导的0的个数