繁体   English   中英

我该如何处理这个 CP 任务?

[英]How can I approach this CP task?

任务(来自保加利亚法官,点击“Език”将其更改为英语):

我得到了 N 种珊瑚中第一个 (S 1 = A) 的大小。 每个后续珊瑚的大小(S i ,其中 i > 1)使用公式 (B*S i-1 + C)%D 计算,其中 A、B、C 和 D 是一些常数。 我被告知 Nemo 在第 K珊瑚附近(当所有珊瑚的大小按升序排序时)。

上述第 K珊瑚的大小是多少?

我将进行 T 测试,对于其中的每一个,我都会得到 N、K、A、B、C 和 D,并提示 output 第 K珊瑚的大小。

要求:

1≤T≤3

1≤K≤N≤10 7

0 ≤ A < D ≤ 10 18

1 ≤ C, B*D ≤ 10 18

Memory 可用为64 MB

时间限制为1.9 秒

我遇到的问题:

对于最坏的情况,我将需要 10 7 *8B,即76 MB

如果可用的 memory 至少为80 MB ,解决方案将是:

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

using biggie = long long;

int main() {
    int t;
    std::cin >> t;
    int i, n, k, j;
    biggie a, b, c, d;
    std::vector<biggie>::iterator it_ans;
    for (i = 0; i != t; ++i) {
        std::cin >> n >> k >> a >> b >> c >> d;
        std::vector<biggie> lut{ a };
        lut.reserve(n);
        for (j = 1; j != n; ++j) {
            lut.emplace_back((b * lut.back() + c) % d);
        }
        it_ans = std::next(lut.begin(), k - 1);
        std::nth_element(lut.begin(), it_ans, lut.end());
        std::cout << *it_ans << '\n';
    }
    return 0;
}

问题 1 :鉴于上述要求,我如何处理此 CP 任务?

问题 2 :是否有可能使用std::nth_element来解决它,因为我无法存储所有 N 个元素? 我的意思是在滑动 window 技术中使用std::nth_element (如果可能的话)。

@克里斯蒂安·斯洛珀

#include <iostream>
#include <queue>

using biggie = long long;

int main() {
    int t;
    std::cin >> t;
    int i, n, k, j, j_lim;
    biggie a, b, c, d, prev, curr;
    for (i = 0; i != t; ++i) {
        std::cin >> n >> k >> a >> b >> c >> d;
        if (k < n - k + 1) {
            std::priority_queue<biggie, std::vector<biggie>, std::less<biggie>> q;
            q.push(a);
            prev = a;
            for (j = 1; j != k; ++j) {
                curr = (b * prev + c) % d;
                q.push(curr);
                prev = curr;
            }
            for (; j != n; ++j) {
                curr = (b * prev + c) % d;
                if (curr < q.top()) {
                    q.pop();
                    q.push(curr);
                }
                prev = curr;
            }
            std::cout << q.top() << '\n';
        }
        else {
            std::priority_queue<biggie, std::vector<biggie>, std::greater<biggie>> q;
            q.push(a);
            prev = a;
            for (j = 1, j_lim = n - k + 1; j != j_lim; ++j) {
                curr = (b * prev + c) % d;
                q.push(curr);
                prev = curr;
            }
            for (; j != n; ++j) {
                curr = (b * prev + c) % d;
                if (curr > q.top()) {
                    q.pop();
                    q.push(curr);
                }
                prev = curr;
            }
            std::cout << q.top() << '\n';
        }
    }
    return 0;
}

这被接受(成功完成所有 40 次测试。最长时间为 1.4 秒,对于 T=3 和 D≤10^9 的测试。对于具有较大 D(因此 T=1)的测试的最长时间为 0.7 秒。)

#include <iostream>

using biggie = long long;

int main() {
    int t;
    std::cin >> t;
    int i, n, k, j;
    biggie a, b, c, d;
    for (i = 0; i != t; ++i) {
        std::cin >> n >> k >> a >> b >> c >> d;
        biggie prefix = 0;
        for (int shift = d > 1000000000 ? 40 : 20; shift >= 0; shift -= 20) {
            biggie prefix_mask = ((biggie(1) << (40 - shift)) - 1) << (shift + 20);
            int count[1 << 20] = {0};
            biggie s = a;
            int rank = 0;
            for (j = 0; j != n; ++j) {
                biggie s_vs_prefix = s & prefix_mask;
                if (s_vs_prefix < prefix)
                    ++rank;
                else if (s_vs_prefix == prefix)
                    ++count[(s >> shift) & ((1 << 20) - 1)];
                s = (b * s + c) % d;
            }
            int i = -1;
            while (rank < k)
                rank += count[++i];
            prefix |= biggie(i) << shift;
        }
        std::cout << prefix << '\n';
    }
    return 0;
}

结果是一个 60 位数字。 我首先通过数字确定高 20 位,然后在另一遍中确定中间 20 位,然后在另一遍中确定低 20 位。

对于高 20 位,生成所有数字并计算每个高 20 位模式出现的频率。 之后,将计数相加,直到达到 K。达到 K 的模式,该模式涵盖了第 K 个最大的数字。 换句话说,就是结果的高 20 位。

中间和低 20 位的计算方式类似,只是我们考虑了当时已知的前缀(高 20 位或高 + 中 40 位)。 作为一个小优化,当 D 很小时,我跳过计算高 20 位。 这让我从 2.1 秒减少到 1.4 秒。

这个解决方案就像 user3386109 描述的,除了桶大小是 2^20 而不是 10^6,所以我可以使用位运算而不是除法,并考虑位模式而不是范围。

对于您遇到的 memory 约束:

(B*Si-1 + C)%D

只需要其自身之前的值 (Si-2)。 所以你可以成对地计算它们,只使用你需要的总数的 1/2。 这只需要索引偶数并为奇数迭代一次。 所以你可以只使用半长 LUT 并计算飞行中的奇数值。 现代 CPU 的速度足以进行此类额外计算。

std::vector<biggie> lut{ a_i,a_i_2,a_i_4,... };
a_i_3=computeOddFromEven(lut[1]);

您也可以像 4,8 那样迈出更长的步伐。 如果数据集很大,RAM 延迟会很大。 所以这就像在整个数据搜索空间中设置检查点以在 memory 和核心使用之间取得平衡。 1000 距离的检查点会将大量 CPU 周期投入重新计算,但随后阵列将适合 CPU 的 L2/L1 缓存,这还不错。 排序时,每个元素的最大重新计算迭代次数现在为 n=1000。 O(1000 x size) 也许它是一个很大的常量,但如果某些常量真的是const ,编译器可能会以某种方式对其进行优化?


如果 CPU 性能再次成为问题:

  • 编写一个编译 function 将您的源代码与用户给字符串的所有“常量”一起编写

  • 使用命令行编译代码(假设目标计算机可以从命令行访问一些代码,例如主程序中的 g++)

  • 运行它并获得结果

当这些在编译时真正保持不变而不是依赖于std::cin时,编译器应该启用更多的速度/内存优化。


如果您确实需要对 RAM 使用量添加硬限制,则使用后备存储实现一个简单的缓存作为您的繁重计算,使用蛮力 O(N^2)(或 O(L x N),每个检查点L 元素与第一种方法相同,其中 L=2 或 4,或...)。

这是一个具有 8M long-long 值空间的直接映射缓存示例:

int main()
{
    std::vector<long long> checkpoints = { 
           a_0, a_16, a_32,...
    };
    auto cacheReadMissFunction = [&](int key){
        // your pure computational algorithm here, helper meant to show variable 
        long long result = checkpoints[key/16];  
        for(key - key%16 times)
            result = iterate(result);
        return result;
    };
    auto cacheWriteMissFunction = [&](int key, long long value){
        /* not useful for your algorithm as it doesn't change behavior per element */
        // backing_store[key] = value;
    };    

    // due to special optimizations, size has to be 2^k
    int cacheSize = 1024*1024*8;
    DirectMappedCache<int, long long> cache(cacheSize,cacheReadMissFunction,cacheWriteMissFunction);
    std::cout << cache.get(20)<<std::endl;
    return 0;
}

如果您使用缓存友好的排序算法,直接缓存访问将使比较中的几乎所有元素大量重复使用,如果您通过像双音一样的方式将元素一个一个地填充到 output 缓冲区/终端 -排序路径(在编译时已知)。 如果这不起作用,那么您可以尝试访问文件作为缓存的“后备存储”,以便一次对整个数组进行排序。 是否禁止使用文件系统? 那么上面的在线编译方式也不行。

直接映射缓存的实现(如果您使用任何cache.set()方法,请不要忘记在您的算法完成后调用flush() ):

#ifndef DIRECTMAPPEDCACHE_H_
#define DIRECTMAPPEDCACHE_H_

#include<vector>
#include<functional>
#include<mutex>
#include<iostream>

/* Direct-mapped cache implementation
 * Only usable for integer type keys in range [0,maxPositive-1]
 *
 * CacheKey: type of key (only integers: int, char, size_t)
 * CacheValue: type of value that is bound to key (same as above)
 */
template<   typename CacheKey, typename CacheValue>
class DirectMappedCache
{
public:
    // allocates buffers for numElements number of cache slots/lanes
    // readMiss:    cache-miss for read operations. User needs to give this function
    //              to let the cache automatically get data from backing-store
    //              example: [&](MyClass key){ return redis.get(key); }
    //              takes a CacheKey as key, returns CacheValue as value
    // writeMiss:   cache-miss for write operations. User needs to give this function
    //              to let the cache automatically set data to backing-store
    //              example: [&](MyClass key, MyAnotherClass value){ redis.set(key,value); }
    //              takes a CacheKey as key and CacheValue as value
    // numElements: has to be integer-power of 2 (e.g. 2,4,8,16,...)
    DirectMappedCache(CacheKey numElements,
                const std::function<CacheValue(CacheKey)> & readMiss,
                const std::function<void(CacheKey,CacheValue)> & writeMiss):size(numElements),sizeM1(numElements-1),loadData(readMiss),saveData(writeMiss)
    {
        // initialize buffers
        for(size_t i=0;i<numElements;i++)
        {
            valueBuffer.push_back(CacheValue());
            isEditedBuffer.push_back(0);
            keyBuffer.push_back(CacheKey()-1);// mapping of 0+ allowed
        }
    }



    // get element from cache
    // if cache doesn't find it in buffers,
    // then cache gets data from backing-store
    // then returns the result to user
    // then cache is available from RAM on next get/set access with same key
    inline
    const CacheValue get(const CacheKey & key)  noexcept
    {
        return accessDirect(key,nullptr);
    }

    // only syntactic difference
    inline
    const std::vector<CacheValue> getMultiple(const std::vector<CacheKey> & key)  noexcept
    {
        const int n = key.size();
        std::vector<CacheValue> result(n);

        for(int i=0;i<n;i++)
        {
            result[i]=accessDirect(key[i],nullptr);
        }
        return result;
    }


    // thread-safe but slower version of get()
    inline
    const CacheValue getThreadSafe(const CacheKey & key)  noexcept
    {
        std::lock_guard<std::mutex> lg(mut);
        return accessDirect(key,nullptr);
    }

    // set element to cache
    // if cache doesn't find it in buffers,
    // then cache sets data on just cache
    // writing to backing-store only happens when
    //                  another access evicts the cache slot containing this key/value
    //                  or when cache is flushed by flush() method
    // then returns the given value back
    // then cache is available from RAM on next get/set access with same key
    inline
    void set(const CacheKey & key, const CacheValue & val) noexcept
    {
        accessDirect(key,&val,1);
    }

    // thread-safe but slower version of set()
    inline
    void setThreadSafe(const CacheKey & key, const CacheValue & val)  noexcept
    {
        std::lock_guard<std::mutex> lg(mut);
        accessDirect(key,&val,1);
    }

    // use this before closing the backing-store to store the latest bits of data
    void flush()
    {
        try
        {
            std::lock_guard<std::mutex> lg(mut);
            for (size_t i=0;i<size;i++)
            {
                if (isEditedBuffer[i] == 1)
                {
                    isEditedBuffer[i]=0;
                    auto oldKey = keyBuffer[i];
                    auto oldValue = valueBuffer[i];
                    saveData(oldKey,oldValue);
                }
            }
        }catch(std::exception &ex){ std::cout<<ex.what()<<std::endl; }
    }

    // direct mapped access
    // opType=0: get
    // opType=1: set
    CacheValue const accessDirect(const CacheKey & key,const CacheValue * value, const bool opType = 0)
    {

        // find tag mapped to the key
        CacheKey tag = key & sizeM1;

        // compare keys
        if(keyBuffer[tag] == key)
        {
            // cache-hit

            // "set"
            if(opType == 1)
            {
                isEditedBuffer[tag]=1;
                valueBuffer[tag]=*value;
            }

            // cache hit value
            return valueBuffer[tag];
        }
        else // cache-miss
        {
            CacheValue oldValue = valueBuffer[tag];
            CacheKey oldKey = keyBuffer[tag];

            // eviction algorithm start
            if(isEditedBuffer[tag] == 1)
            {
                // if it is "get"
                if(opType==0)
                {
                    isEditedBuffer[tag]=0;
                }

                saveData(oldKey,oldValue);

                // "get"
                if(opType==0)
                {
                    const CacheValue && loadedData = loadData(key);
                    valueBuffer[tag]=loadedData;
                    keyBuffer[tag]=key;
                    return loadedData;
                }
                else /* "set" */
                {
                    valueBuffer[tag]=*value;
                    keyBuffer[tag]=key;
                    return *value;
                }
            }
            else // not edited
            {
                // "set"
                if(opType == 1)
                {
                    isEditedBuffer[tag]=1;
                }

                // "get"
                if(opType == 0)
                {
                    const CacheValue && loadedData = loadData(key);
                    valueBuffer[tag]=loadedData;
                    keyBuffer[tag]=key;
                    return loadedData;
                }
                else // "set"
                {
                    valueBuffer[tag]=*value;
                    keyBuffer[tag]=key;
                    return *value;
                }
            }

        }
    }


private:
    const CacheKey size;
    const CacheKey sizeM1;
    std::mutex mut;

    std::vector<CacheValue> valueBuffer;
    std::vector<unsigned char> isEditedBuffer;
    std::vector<CacheKey> keyBuffer;
    const std::function<CacheValue(CacheKey)>  loadData;
    const std::function<void(CacheKey,CacheValue)>  saveData;

};


#endif /* DIRECTMAPPEDCACHE_H_ */

您可以使用最大堆解决此问题。

  1. 将前 k 个元素插入最大堆。 这些 k 中最大的元素现在将位于根部。
  2. 对于每个剩余元素e
    eroot进行比较。
    如果e大于根,则丢弃它。 如果e小于root ,则删除root并将e插入堆结构。
  3. 处理完所有元素后,第 k 个最小的元素位于root中。

该方法使用O(K)空间和O(n log n)时间。

有一种人们经常称之为 LazySelect 的算法,我认为它在这里是完美的。

我们很有可能会进行两次通过。 在第一遍中,我们保存了一个大小为 n 的随机样本,其大小远小于 N。答案将在排序样本中的索引 (K/N)n 附近,但由于随机性,我们必须小心。 将值 a 和 b 保存在 (K/N)n ± r,其中 r 是 window 的半径。在第二遍中,我们将所有值保存在 [a, b] 中,计算值的数量减去比 a(让它是 L),和 select 索引 K−L 的值,如果它在 window 中(否则,再试一次)。

选择 n 和 r 的理论建议很好,但我在这里会很务实。 选择 n 以便您使用大部分可用的 memory; 样本越大,信息越多。 选择 r 也相当大,但由于随机性,没有那么激进。

C++ 下面的代码。 在在线判断上,它比凯利的快(T=3 测试最多 1.3 秒,T=1 测试最多 0.5 秒)。

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <optional>
#include <random>
#include <vector>

namespace {

class LazySelector {
public:
  static constexpr std::int32_t kTargetSampleSize = 1000;

  explicit LazySelector() { sample_.reserve(1000000); }

  void BeginFirstPass(const std::int32_t n, const std::int32_t k) {
    sample_.clear();
    mask_ = n / kTargetSampleSize;
    mask_ |= mask_ >> 1;
    mask_ |= mask_ >> 2;
    mask_ |= mask_ >> 4;
    mask_ |= mask_ >> 8;
    mask_ |= mask_ >> 16;
  }

  void FirstPass(const std::int64_t value) {
    if ((gen_() & mask_) == 0) {
      sample_.push_back(value);
    }
  }

  void BeginSecondPass(const std::int32_t n, const std::int32_t k) {
    sample_.push_back(std::numeric_limits<std::int64_t>::min());
    sample_.push_back(std::numeric_limits<std::int64_t>::max());
    const double p = static_cast<double>(sample_.size()) / n;
    const double radius = 2 * std::sqrt(sample_.size());
    const auto lower =
        sample_.begin() + std::clamp<std::int32_t>(std::floor(p * k - radius),
                                                   0, sample_.size() - 1);
    const auto upper =
        sample_.begin() + std::clamp<std::int32_t>(std::ceil(p * k + radius), 0,
                                                   sample_.size() - 1);
    std::nth_element(sample_.begin(), upper, sample_.end());
    std::nth_element(sample_.begin(), lower, upper);
    lower_ = *lower;
    upper_ = *upper;
    sample_.clear();
    less_than_lower_ = 0;
    equal_to_lower_ = 0;
    equal_to_upper_ = 0;
  }

  void SecondPass(const std::int64_t value) {
    if (value < lower_) {
      ++less_than_lower_;
    } else if (upper_ < value) {
    } else if (value == lower_) {
      ++equal_to_lower_;
    } else if (value == upper_) {
      ++equal_to_upper_;
    } else {
      sample_.push_back(value);
    }
  }

  std::optional<std::int64_t> Select(std::int32_t k) {
    if (k < less_than_lower_) {
      return std::nullopt;
    }
    k -= less_than_lower_;
    if (k < equal_to_lower_) {
      return lower_;
    }
    k -= equal_to_lower_;
    if (k < sample_.size()) {
      const auto kth = sample_.begin() + k;
      std::nth_element(sample_.begin(), kth, sample_.end());
      return *kth;
    }
    k -= sample_.size();
    if (k < equal_to_upper_) {
      return upper_;
    }
    return std::nullopt;
  }

private:
  std::default_random_engine gen_;
  std::vector<std::int64_t> sample_ = {};
  std::int32_t mask_ = 0;
  std::int64_t lower_ = std::numeric_limits<std::int64_t>::min();
  std::int64_t upper_ = std::numeric_limits<std::int64_t>::max();
  std::int32_t less_than_lower_ = 0;
  std::int32_t equal_to_lower_ = 0;
  std::int32_t equal_to_upper_ = 0;
};

} // namespace

int main() {
  int t;
  std::cin >> t;
  for (int i = t; i > 0; --i) {
    std::int32_t n;
    std::int32_t k;
    std::int64_t a;
    std::int64_t b;
    std::int64_t c;
    std::int64_t d;
    std::cin >> n >> k >> a >> b >> c >> d;
    std::optional<std::int64_t> ans = std::nullopt;
    LazySelector selector;
    do {
      {
        selector.BeginFirstPass(n, k);
        std::int64_t s = a;
        for (std::int32_t j = n; j > 0; --j) {
          selector.FirstPass(s);
          s = (b * s + c) % d;
        }
      }
      {
        selector.BeginSecondPass(n, k);
        std::int64_t s = a;
        for (std::int32_t j = n; j > 0; --j) {
          selector.SecondPass(s);
          s = (b * s + c) % d;
        }
      }
      ans = selector.Select(k - 1);
    } while (!ans);
    std::cout << *ans << '\n';
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM