[英]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_ */
您可以使用最大堆解决此问题。
e
:e
与root
进行比较。e
大于根,则丢弃它。 如果e
小于root
,则删除root
并将e
插入堆结构。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.