算法:计算每个更新查询的不同排序子序列的最小数量

时间:2018-01-27 05:41:25

标签: algorithm

这个问题在接受采访时向我询问:
包含相邻值的不同分类子序列定义为长度为1 ,或者仅在排序时包含相邻数字。每个元素只能属于1个这样的子序列。我有Q查询,每个都更新A中的单个值,我必须回答每个查询,如果零件数量最小化,A分区中有多少部分进入不同的排序子序列。

例如,A = [1,2,3,4,3,5]的零件数量可以通过以下两种方式进行分割来最小化,两种方式都只包含两部分:

1) [1,2,3] && [4,3,5] ==> answer 2 (4,3,5 sorted is 3,4,5 all adjacent values)

2) [1,2,3,4,5] && [3] ==> answer 2 

方法我尝试过:哈希和组合集但由于超时而未清除所有测试用例。

问题规则PDF: PDF

约束:
1<= N,Q <= 3*10^5
1< A(i)< 10^9

2 个答案:

答案 0 :(得分:1)

在第一次传递时保留一个群集列表。每个都有一组值,具有最小值和最大值。这些集群可以很好地存储在段树中(以便在它们触摸时很容易合并)。

循环显示N个数字,并为每个数字添加到现有群集(可能触发合并),或创建新群集。如果您的群集存储min-1和max + 1,这可能会更容易。

现在你完成了N个数字的初始输入,并且你有几个集群,所有集群都可能具有合理的基数排序。

但是,您不想完成基数排序。生成计数列表,然后使用它来计算相邻的簇。循环遍历这一点,每次计数减少时,您都发现(差异)许多最终不同的排序子序列。 (使用max + 1再次付清,因为最后保证的零意味着您不必在循环后添加特殊情况。)

答案 1 :(得分:1)

预处理

首先,你可以在所有查询之前预处理A并生成一个表(比如times_of),这样当给出一个数字n时,可以有效地获得次数{{1}通过像n这样的表达式出现在A中。在以下示例中,假设times_of[n]的类型为A,我们使用int[N]来实现该表。其建设成本为O(NlogN)时间。

std::map

auto preprocess(int *begin, int *end) { std::map<int, std::size_t> times_of; while (begin != end) { ++times_of[*begin]; ++begin; } return times_of; } min分别成为max的最小和最大元素。然后适用以下引理:

  

不同排序子序列的最小数量等于max {0,A - times_of[min]} + ... + max {0,times_of[min-1] -    times_of[max]}。

严格的证据有点技术性,所以我从这个答案中省略了它。粗略地说,考虑从小到大的数字。如果times_of[max-1]显示超过n,则必须带来额外的n-1个子序列。

通过这个引理,我们可以在O(N)时间内初始计算不同的排序子序列times_of[n]-times_of[n-1]的最小数量(通过迭代result,而不是从times_of迭代到{ {1}})。以下是示例代码:

min

查询

要处理查询max,我们首先更新费用为O(logN)时间的std::size_t result = 0; auto prev = std::make_pair(min - 1, static_cast<std::size_t>(0)); for (auto &cur : times_of) { // times_of[cur.first-1] == 0 if (cur.first != prev.first + 1) result += cur.second; // times_of[cur.first-1] == times_of[prev.first] else if (cur.second > prev.second) result += cur.second - prev.second; prev = cur; } A[u] = v。然后根据引理,我们只需重新计算常数(即4)相关项以更新times_of[A[u]]。每次重新计算都花费O(logN)时间(以查找times_of[v]中的上一个或下一个元素),因此查询总共需要O(logN)时间。