我需要一些关于这个C++算法的帮助

I require some assistance with this C++ algorithm

本文关键字:C++ 算法 帮助 于这个      更新时间:2023-10-16

我试图解决一个算法问题,但找不到解决方案。。。

任务是输出达到灯的特定配置所需的最低步数。

存在两排灯,并且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] = 1RU[3] = 3RU[4] = 0

您可以在O(n)时间内从左到右计算RU和RL。

观察

首先,观察到如果A[i][j]在上排的末尾有k_1零,在下排的末端有k_2零,那么f(i, j) = f(i - k_1, j - k_2),因为最后的k_1k_2灯已经熄灭

递归关系

观察一下,如果你想计算f(i, j),有三种情况:

  1. 关闭上排1的最大连续跑动
  2. 关闭下一行1的最大连续跑动
  3. 如果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

记忆

为了避免在递归调用期间多次为相同的ij计算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 上直播