Reversing a sequence of cyclic shifts

382 views Asked by At

Here's the problem I have a hard time solving.

You are given a ciphertext Y and a sequence of cyclic shifts that had produced Y from string Z, the shift with parameters (i, j, k) applies to the substring Z[i..j] (from i-th to j-th character, inclusive) and cyclicly rotates it to the right k times. String characters are numbered starting from one. Given the above information, your task is to guess the initial cleartext X.

Input:

The first line contains the ciphertext, which is a nonempty string consisting of N lowercase English letters (1 ≤ N ≤ 50000). The second line contains the number of shifts M (1 ≤ M ≤ 50000). The following M lines describe the sequence of cyclic shifts (in the order of their application to the cleartext). Each shift is described by three parameters i, j, k (1 ≤ i < j ≤ N, 1 ≤ k ≤ j − i).

Example of input:

logoduck 
3
1 3 1 
4 5 1 
1 4 1

As output you should provide the deciphered text (``goodluck'' for example).

The obvious approach is to try and reverse each shift starting from the last one. It seems that this approach is not time-efficient. However, I can't come up with any ideas how to do it any other way, so any help is appreciated.

I attach my code:

#include <iostream>
#include <vector>
#include <string>


int main() {

    std::string message;
    std::cin >> message;

    int number_of_elements = message.size();
    int elements[number_of_elements];
    for (int i = 0; i < number_of_elements; ++i) {
        elements[i] = i;
    }

    int number_of_shifts;
    std::cin >> number_of_shifts;
    std::vector<std::vector<int>> shifts(number_of_shifts);

    for (int iterator = 0; iterator < number_of_shifts; ++iterator) {
        int left, right, by;
        std::cin >> left >> right >> by;
        --left;
        --right;
        shifts[iterator].push_back(left);
        shifts[iterator].push_back(right);
        shifts[iterator].push_back(by);
    }

    for (int iterator = number_of_shifts - 1; -1 < iterator; --iterator) {
        int current[number_of_elements];
        int left, right, by;
        left = shifts[iterator][0];
        right = shifts[iterator][1];
        by = shifts[iterator][2];


        for (int j = right; left - 1 < j; --j) {
            if (j - by < left) {
                current[right + 1 - (left - (j - by))] = elements[j];
            } else {
                current[j - by] = elements[j];
            }
        }

        for (int j = left; j < right + 1; ++j) {
            elements[j] = current[j];
        }
    }

    for (int i = 0; i < number_of_elements; ++i) {
        std::cout <<  message.substr(elements[i], 1);
    }

    return 0;
}
1

There are 1 answers

1
Matt Timmermans On

You can do this in O(M log N) time using a data structure called a "rope", which is like a string that supports splitting and concatenation in O(log N) time: https://en.wikipedia.org/wiki/Rope_(data_structure)

Rotations can be build from these operations, of course.

The implementation is a binary tree, B-tree, or similar with limited-size strings in the leaves.

It's not hard to find C++ implementations, but unfortunately, the STL doesn't have one. If you have to implement it yourself, it's a little tricky.