我需要一些关于这个C++算法的帮助
I require some assistance with this C++ algorithm
我试图解决一个算法问题,但找不到解决方案。。。
任务是输出达到灯的特定配置所需的最低步数。
存在两排灯,并且N<10000列,类似:
11011
11011
或
11101101111000101010
01111101100000010100
这些灯可以"打开"(1)或"关闭"(0)。
从全部关闭(0)开始,程序必须输出达到所需配置所需的步骤数。
一个步骤可以是:
- 拨动一盏灯
- 拨动两盏灯,一个在另一个上面(在同一列中)
- 在同一行中切换n个连续的灯,可以是整行,也可以只有两个(或如上所述的一个)
我认为算法应该简单地计算完全关灯所需的步骤数,这与"正确"的顺序相同。此外,我的猜测是试图找到"洞",即具有相同状态的多个灯的序列,然后切换这些洞。但由于有两排,情况变得复杂起来。。。
然而,在那之后我完全迷失了方向,我需要一些帮助。。。
编辑:
OP最近发布了一个原始问题声明的链接,结果发现你可以来回切换灯。我下面的解决方案只有在你被允许只打开灯的情况下才有效。
定义
让我们定义一下:
U[i] := i-th light in the upper row.
L[i] := i-th light in the lower row.
A[i][j] := subconfiguration of the input configuration where you have i lamps in the upper row and j lamps in the lower row.
例如,如果启动状态为:
11101101111000101010
01111101100000010100
则A[5][2]
为:
11101
01
其次,让我们定义一下:
f(i, j) := minimum number of moves to switch all lights off in A[i][j]
您对计算f(n, n)
感兴趣
此外,让我们定义一下:
RU[i] := maximal consecutive run of 1's in the upper row ending in the i-th position.
RL[i] := maximal consecutive run of 1's in the lower row ending in the i-th position.
例如,如果启动状态为:
11101101111000101010
01111101100000010100
然后是RU[1] = 1
、RU[3] = 3
、RU[4] = 0
您可以在O(n)
时间内从左到右计算RU和RL。
观察
首先,观察到如果A[i][j]
在上排的末尾有k_1
零,在下排的末端有k_2
零,那么f(i, j) = f(i - k_1, j - k_2)
,因为最后的k_1
和k_2
灯已经熄灭
递归关系
观察一下,如果你想计算f(i, j)
,有三种情况:
- 关闭上排1的最大连续跑动
- 关闭下一行1的最大连续跑动
- 如果i=j并且灯U[i]和L[j]打开,那么你可以一次关闭这两个灯
当然,基本情况是f(0, 0)
,它需要0次移动。
然后为了计算f(i, j)
:
if U[i] is switched off: //skip zeros at the end of the upper row
compute f(i - 1, j)
else if L[j] is switched off: //skip zeros at the end of the lower row
compute f(i, j - 1)
else
if i == j // U[i] and L[j] are switched on because we skipped zeros at the end
f(i, j) = min(f(i - RU[i], j), f(i, j - RL[j]), f(i - 1, j - 1)) + 1
else:
f(i, j) = min(f(i - RU[i], j), f(i, j - RL[j])) + 1
记忆
为了避免在递归调用期间多次为相同的i
和j
计算f
,只需将已计算的f
的结果存储在哈希表中,并在O(1)
中返回,而不是再次计算。
运行时
简单的上界当然是O(n^2)
,因为最多有O(n^2)
个子问题。
这是
std::bitset
的一个好例子
好吧,由于我第一次严重误解了,我决定做一个简单的启发式。
////////////////////////////////////////////////////
// The solver with a single, simple heuristic:
//
// If there's a hole in a range for row1 where row2 is to have a `1`, we might
// benefit from toggling both rows in advance, because it might result in a
// longer stretch to toggle in the first row
//
// An obvious improvement would to be to try with rows swapped as well.
//
// (As a bonus, all solutions are verified)
int solve(std::ostream& os, bitset row1, bitset row2)
{
auto candidates = row2 & ~row1;
int best_count = row1.size() + 1; // or INT_MAX or similar
bitset best_edits;
for (auto const& edits : combinations(candidates))
{
std::stringstream steps_stream;
int count = emit_steps(steps_stream, row1, row2, edits);
assert(verify(steps_stream, row1, row2, false));
if (count < best_count)
{
best_edits = edits;
best_count = count;
}
}
return emit_steps(os, row1, row2, best_edits);
}
这个solve(...)
方法现在发出一个步骤脚本,它通过我最初的答案中的解释器(的修改版本)进行验证
// test driver reading the target configuration from stdin
// and displaying the 'best found' solution with intermediate steps
int main()
{
bitset row1, row2;
if (std::cin >> row1 >> row2)
{
std::stringstream steps;
int number = solve(steps, row1, row2);
std::cout << "Best candidate found results in " << number << " steps:n";
verify(steps, row1, row2, true);
}
}
输出:
Best candidate found results in 8 steps:
Start verify
after 'toggle both 2':
row1: 00000000000000000000000000000100
row2: 00000000000000000000000000000100
after 'toggle both 4':
row1: 00000000000000000000000000010100
row2: 00000000000000000000000000010100
after 'toggle first from 1 through 5':
row1: 00000000000000000000000000101010
row2: 00000000000000000000000000010100
after 'toggle first from 9 through 12':
row1: 00000000000000000001111000101010
row2: 00000000000000000000000000010100
after 'toggle first from 14 through 15':
row1: 00000000000000001101111000101010
row2: 00000000000000000000000000010100
after 'toggle first from 17 through 19':
row1: 00000000000011101101111000101010
row2: 00000000000000000000000000010100
after 'toggle second from 11 through 12':
row1: 00000000000011101101111000101010
row2: 00000000000000000001100000010100
after 'toggle second from 14 through 18':
row1: 00000000000011101101111000101010
row2: 00000000000001111101100000010100
Done
完整演示程序:Coliru直播
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/dynamic_bitset.hpp>
#include <iostream>
#include <sstream>
using bitset = boost::dynamic_bitset<>;
// bitset helpers
int count_ranges(bitset const& bs);
std::vector<bitset> combinations(bitset const& bs);
// generate the steps script
int emit_apply_both (std::ostream& os, bitset const& edits);
int emit_toggles (std::ostream& os, bitset const& row, std::string const& row_name);
int emit_steps (std::ostream& os, bitset const& row1, bitset const& row2, bitset const& edits);
// applies a steps script from scratch and verifies the result
// (optionally tracing all steps along the way)
bool verify(std::istream& is, bitset const& target1, bitset const& target2, bool verbose);
////////////////////////////////////////////////////
// The solver with a single, simple heuristic:
//
// If there's a hole in a range for row1 where row2 is to have a `1`, we might
// benefit from toggling both rows in advance, because it might result in a
// longer stretch to toggle in the first row
//
// An obvious improvement would to be to try with rows swapped as well.
//
// (As a bonus, all solutions are verified)
int solve(std::ostream& os, bitset row1, bitset row2)
{
auto candidates = row2 & ~row1;
int best_count = row1.size() + 1; // or INT_MAX or similar
bitset best_edits;
for (auto const& edits : combinations(candidates))
{
std::stringstream steps_stream;
int count = emit_steps(steps_stream, row1, row2, edits);
assert(verify(steps_stream, row1, row2, false));
if (count < best_count)
{
best_edits = edits;
best_count = count;
}
}
return emit_steps(os, row1, row2, best_edits);
}
// test driver reading the target configuration from stdin
// and displaying the 'best found' solution with intermediate steps
int main()
{
bitset row1, row2;
if (std::cin >> row1 >> row2)
{
std::stringstream steps;
int number = solve(steps, row1, row2);
std::cout << "Best candidate found results in " << number << " steps:n";
verify(steps, row1, row2, true);
}
}
////////////////////////////////////////////////////
/// details, helpers
int count_ranges(bitset const& bs)
{
int count = 0;
for (auto bit=bs.find_first(); bit!=bitset::npos; bit=bs.find_next(bit))
{
do ++bit; while (bit<=bs.size() && bs[bit]);
++count;
}
return count;
}
std::vector<bitset> combinations(bitset const& bs)
{
bitset accum(bs.size());
std::vector<bitset> result;
std::function<void(size_t bit)> recurse = [&](size_t bit) mutable
{
if (bit == bitset::npos)
result.push_back(accum);
else
{
accum.flip(bit); recurse(bs.find_next(bit));
accum.flip(bit); recurse(bs.find_next(bit));
}
};
return recurse(bs.find_first()), result;
}
int emit_toggles(std::ostream& os, bitset const& row, std::string const& row_name)
{
int count = 0;
for (auto start=row.find_first(); start!=bitset::npos; start=row.find_next(start))
{
auto end = start;
do ++end; while (end<row.size() && row[end]);
if (start+1 == end)
os << "toggle " << row_name << " " << start << "n";
else
os << "toggle " << row_name << " from " << start << " through " << (end-1) << "n";
count += 1;
start = end;
}
return count;
}
int emit_apply_both(std::ostream& os, bitset const& edits)
{
for (auto bit=edits.find_first(); bit!=bitset::npos; bit=edits.find_next(bit))
os << "toggle both " << bit << "n";
return edits.count();
}
int emit_steps(std::ostream& os, bitset const& row1, bitset const& row2, bitset const& edits)
{
auto count = emit_apply_both(os, edits);
count += emit_toggles (os, row1 ^ edits, "first");
count += emit_toggles (os, row2 ^ edits, "second");
return count;
}
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
template <typename Lambda> struct WrapAction {
template <typename...> struct result { typedef void type; };
template <typename... T> void operator()(T&&... t) const { _ll(std::forward<T>(t)...); }
WrapAction(Lambda&& ll) : _ll(std::forward<Lambda>(ll)) { }
private:
mutable Lambda _ll;
};
template <typename Lambda> WrapAction<Lambda> make_action(Lambda&& ll) { return { std::forward<Lambda>(ll) }; }
bool verify(std::istream& is, bitset const& target1, bitset const& target2, bool verbose)
{
bitset row1(target1.size()), row2(target2.size());
if (verbose) std::cout << "Start verifyn";
auto toggle1 = make_action([&](int i) mutable { row1.flip(i); });
auto toggle2 = make_action([&](int i) mutable { row2.flip(i); });
auto both = make_action([&](int i) mutable { toggle1(i); toggle2(i); });
auto range1 = make_action([&](int i1, int i2) mutable { while (i2>=i1) toggle1(i2--); });
auto range2 = make_action([&](int i1, int i2) mutable { while (i2>=i1) toggle2(i2--); });
// for statement tracing:
typedef boost::spirit::istream_iterator It;
auto trace = make_action([&](boost::iterator_range<It> const& raw_iterators) mutable {
if (verbose) {
std::cout << "after '" << std::string(raw_iterators.begin(), raw_iterators.end()) << "':n";
std::cout << " row1:t" << row1 << "n" << " row2:t" << row2 << "n";
}
});
using namespace boost::spirit::qi;
namespace phx = boost::phoenix;
using phx::bind;
using phx::construct;
is.unsetf(std::ios::skipws);
It f(is), l;
bool ok = phrase_parse(f, l,
- raw [
lit("toggle") >> ("both" >> int_) [ bind(both, _1) ]
| lit("toggle") >> lit("first") >> ("from" >> int_ >> "through" >> int_) [ bind(range1, _1, _2) ]
| lit("toggle") >> lit("second") >> ("from" >> int_ >> "through" >> int_) [ bind(range2, _1, _2) ]
| "toggle" >> lit("first") >> (int_) [ bind(toggle1, _1) ]
| "toggle" >> lit("second") >> (int_) [ bind(toggle2, _1) ]
| eps(false)
] [ bind(trace, _1) ] % eol,
blank);
if (verbose)
{
if (ok) std::cout << "Donen";
else std::cout << "Failedn";
if (f != l) std::cout << "Remaining unparsed: '" << std::string(f,l) << "'n";
}
return ok && (f==l) && (row1==target1) && (row2==target2);
}
最后,重新打开。是时候发布我的答案了:)
该解决方案使用单次迭代O(n+1)
,仅在7个步骤中解决2x20灯的示例
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <cassert>
#include <functional>
using namespace std;
const string upperInput("11101101111000101010");
const string lowerInput("01111101100000010100");
vector<bool> upper, lower;
void init()
{ // convert string rows to vector<bool> rows
for_each(upperInput.begin(), upperInput.end(), [&](char e) { upper.push_back(e == '1'); });
for_each(lowerInput.begin(), lowerInput.end(), [&](char e) { lower.push_back(e == '1'); });
assert(upper.size() == lower.size());
}
void dump()
{ // output current content of vector<bool> rows
for_each(upper.begin(), upper.end(), [] (bool b) { cout << (b ? '1' : '0'); });
cout << endl;
for_each(lower.begin(), lower.end(), [] (bool b) { cout << (b ? '1' : '0'); });
cout << endl;
cout << endl;
}
// iterate over both rows with callback
typedef function<void (const vector<bool>::iterator& itUpper, const vector<bool>::iterator& itLower)> IteratorCallback;
void iterate(const bool includeEnd, const IteratorCallback callback)
{
for (auto itUpper = upper.begin(), itLower = lower.begin(); itUpper != upper.end(); itUpper++, itLower++)
callback(itUpper, itLower);
if (includeEnd)
callback(upper.end(), lower.end());
}
int main()
{
init();
cout << "Initial rows data: " << endl;
dump();
int steps = 0;
// a state is isolated if the state before and after holds the opposite value or is an isolated 1 at the beginning or end.
const auto isIsolatedState = [] (const vector<bool>& source, const vector<bool>::iterator& it) {
return (it != source.begin() && it != source.end() && *(it - 1) != *it && *(it + 1) != *it)
|| (it == source.begin() && *it && !*(it + 1))
|| (it == source.end() && *it && !*(it - 1));
};
// toggle consecutive states in the given range
const auto toggle = [] (const vector<bool>::iterator& begin, const vector<bool>::iterator& end)
{
for (auto it = begin; it != end; it++)
*it = !*it;
};
auto upperBlockStart = upper.front() ? upper.begin() : upper.end();
auto lowerBlockStart = lower.front() ? lower.begin() : lower.end();
iterate(true, [&upperBlockStart, &lowerBlockStart, &steps, isIsolatedState, toggle] (const vector<bool>::iterator& itUpper, const vector<bool>::iterator& itLower) {
// toggle columns if state in both rows is isolated
if (itUpper != upper.end())
{
const int column = itUpper - upper.begin() + 1;
if (isIsolatedState(upper, itUpper) && isIsolatedState(lower, itLower))
{
cout << "#" << ++steps << ": Toggling column " << column << endl;
toggle(itUpper, itUpper + 1);
toggle(itLower, itLower + 1);
dump();
}
}
// keep track of blocks with 1's in upper row
const bool upperState = itUpper != upper.end() ? *itUpper : false;
if (upperState && upperBlockStart == upper.end())
upperBlockStart = itUpper; // start of block of 1's in upper row
if (!upperState && upperBlockStart != upper.end())
{ // end of block of 1's in upper row
const int count = itUpper - upperBlockStart;
const int column = upperBlockStart - upper.begin() + 1;
cout << "#" << ++steps << ": Toggling " << count << " lamp(s) in upper row starting from column " << column << endl;
toggle(upperBlockStart, itUpper);
upperBlockStart = upper.end();
dump();
}
// keep track of blocks with 1's in lower row
const bool lowerState = itLower != lower.end() ? *itLower : false;
if (lowerState && *itLower && lowerBlockStart == lower.end())
lowerBlockStart = itLower; // start of block of 1's in lower row
if (!lowerState && lowerBlockStart != lower.end())
{ // end of block of 1's in lower row
const int count = itLower - lowerBlockStart;
const int column = lowerBlockStart - lower.begin() + 1;
cout << "#" << ++steps << ": Toggling " << count << " lamp(s) in lower row starting from column " << column << endl;
toggle(lowerBlockStart, itLower);
lowerBlockStart = lower.end();
dump();
}
});
cout << "Solved in " << steps << " step(s)" << endl;
return 0;
}
查看它在coliru 上的工作
这是我的解决方案。O(N
)时间,单程。(如果您更改输入格式以一次接受一列,甚至可以适用于O(1)存储。)添加评论并证明其正确性是读者的练习。
#include <cstdlib>
#include <iostream>
#include <vector>
#include <array>
int main()
{
std::array<std::vector<bool>, 2> lamps;
auto row_iter = lamps.begin();
char c;
while (std::cin.get(c) && row_iter != lamps.end()) {
switch (c){
case '0':
row_iter->push_back(false);
break;
case '1':
row_iter->push_back(true);
break;
case 'n':
++row_iter;
break;
default:
std::cerr << "Unexpected input char "
<< static_cast<int>(c) << std::endl;
return EXIT_FAILURE;
}
}
std::vector<bool>& row1 = lamps[0];
std::vector<bool>& row2 = lamps[1];
if (row1.size() != row2.size()) {
std::cerr << "Rows must be the same length" << std::endl;
return EXIT_FAILURE;
}
row1.push_back(false);
row2.push_back(false);
unsigned int col_flips = 0;
unsigned int changes = 0;
bool prev1 = false, prev2 = false, both_changed = false;
for (auto iter1=row1.cbegin(), iter2=row2.cbegin();
iter1 != row1.cend() && iter2 != row2.cend();
++iter1, ++iter2) {
unsigned int col_changes = (*iter1 != prev1);
col_changes += (*iter2 != prev2);
if (col_changes == 2) {
if (both_changed) {
changes -= 2;
++col_flips;
both_changed = false;
} else {
changes += col_changes;
both_changed = true;
}
} else {
changes += col_changes;
both_changed = false;
}
prev1 = *iter1;
prev2 = *iter2;
}
std::cout << col_flips + changes/2 << std::endl;
return EXIT_SUCCESS;
}
在coliru 上直播
- 为什么这个运算符<重载函数对 STL 算法不可见?
- 基于ELO的团队匹配算法
- C++选择排序算法中的逻辑错误
- 有没有办法将谓词中的元素偏移量传递给 std 算法?
- C++A*算法并不总是在路径中具有目标节点
- 排序算法c++
- 构建可组合有向图(扫描仪生成器的汤普森构造算法)
- 算法问题:查找从堆栈中弹出的所有序列
- 下面是排序算法O(n)吗
- KMP算法和LPS表构造的运行时间
- 为什么我的排序算法会更改数组值
- 求最大元素位置的分治算法
- 具有非整数边容量的最大流量的Dinic算法
- 到连接组件算法的问题(递归)
- STL算法函数在多个一维容器上的使用
- 读取最后一行代码算法 - c++ 时出现问题
- 括号更改 O(n) 算法
- std::unordered_map 搜索算法是如何实现的?
- 如何实现高效的算法来计算大型数据集的多个不同值?
- 如何在 Mac 上使用 c++17 并行标准库算法?