দক্ষ স্ট্রিং ট্রান্সকিশন অ্যালগরিদম, ক্রমান্বয়ে সমান উপসর্গ এবং প্রত্যয়গুলি মুছে ফেলা হচ্ছে


11

পরীক্ষায় সময় সীমা: 5 সেকেন্ড
পরীক্ষায় মেমরি সীমা: 512 মেগাবাইট

আপনাকে sদৈর্ঘ্যের একটি স্ট্রিং দেওয়া হবে n( n5000 ডলার)। আপনি এই স্ট্রিংয়ের যথাযথ উপসর্গটি নির্বাচন করতে পারেন যা এটির প্রত্যয় এবং নির্বাচিত উপসর্গ বা সংশ্লিষ্ট প্রত্যয়টি সরিয়ে ফেলতে পারেন। তারপরে আপনি ফলস্বরূপ স্ট্রিংয়ের সাথে একটি অ্যানালগাস অপারেশন প্রয়োগ করতে পারেন। চূড়ান্ত স্ট্রিংয়ের সর্বনিম্ন দৈর্ঘ্য কত, এ জাতীয় অপারেশনগুলির সর্বোত্তম ক্রম প্রয়োগ করার পরে এটি অর্জন করা যেতে পারে?

ইনপুট
প্রতিটি পরীক্ষার প্রথম লাইনে একটি স্ট্রিং থাকে sযা ছোট ইংরেজি বর্ণগুলি নিয়ে থাকে।

আউটপুট
একটি একক পূর্ণসংখ্যা - চূড়ান্ত স্ট্রিংয়ের সর্বনিম্ন দৈর্ঘ্য, যা এই জাতীয় ক্রিয়াকলাপগুলির সর্বোত্তম ক্রম প্রয়োগ করার পরে অর্জন করা যেতে পারে।

উদাহরণ +-------+--------+----------------------------------+ | Input | Output | Explanation | +-------+--------+----------------------------------+ | caaca | 2 | caaca → ca|aca → aca → ac|a → ac | +-------+--------+----------------------------------+ | aabaa | 2 | aaba|a → a|aba → ab|a → ab | +-------+--------+----------------------------------+ | abc | 3 | No operations are possible | +-------+--------+----------------------------------+

আমি এখন পর্যন্ত যা করতে পেরেছি তা এখানে:

  1. ও (n ^ 2) এ প্রদত্ত স্ট্রিংয়ের সমস্ত সাবস্ট্রিংয়ের উপসর্গ কার্য গণনা করুন

  2. ও (n ^ 3) এ সমস্ত সম্ভাব্য সংযোজন সম্পাদনের ফলাফলটি পরীক্ষা করে দেখুন

আমার সমাধানটি সমস্ত পরীক্ষায় n2000 ডলারে পাস করে তবে 2000 < n≤ 5000 এ সময় সীমা অতিক্রম করে its এটির কোডটি এখানে:

#include <iostream>
#include <string>

using namespace std;

const int MAX_N = 5000;

int result; // 1 less than actual

// [x][y] corresponds to substring that starts at position `x` and ends at position `x + y` =>
// => corresponding substring length is `y + 1`
int lps[MAX_N][MAX_N]; // prefix function for the substring s[x..x+y]
bool checked[MAX_N][MAX_N]; // whether substring s[x..x+y] is processed by check function

// length is 1 less than actual
void check(int start, int length) {
    checked[start][length] = true;
    if (length < result) {
        if (length == 0) {
            cout << 1; // actual length = length + 1 = 0 + 1 = 1
            exit(0); // 1 is the minimum possible result
        }
        result = length;
    }
    // iteration over all proper prefixes that are also suffixes
    // i - current prefix length
    for (int i = lps[start][length]; i != 0; i = lps[start][i - 1]) {
        int newLength = length - i;
        int newStart = start + i;
        if (!checked[start][newLength])
            check(start, newLength);
        if (!checked[newStart][newLength])
            check(newStart, newLength);
    }
}

int main()
{
    string str;
    cin >> str;
    int n = str.length();
    // lps calculation runs in O(n^2)
    for (int l = 0; l < n; l++) {
        int subLength = n - l;
        lps[l][0] = 0;
        checked[l][0] = false;
        for (int i = 1; i < subLength; ++i) {
            int j = lps[l][i - 1];
            while (j > 0 && str[i + l] != str[j + l])
                j = lps[l][j - 1];
            if (str[i + l] == str[j + l])  j++;
            lps[l][i] = j;
            checked[l][i] = false;
        }
    }
    result = n - 1;
    // checking all possible operations combinations in O(n^3)
    check(0, n - 1);
    cout << result + 1;
}

প্রশ্ন: আরও কার্যকর সমাধান আছে কি?


5
আমি মনে করি কোড রিভিউ স্ট্যাক এক্সচেঞ্জ এর জন্য আরও ভাল হবে। যাই হোক না কেন সুন্দর এবং স্পষ্ট প্রশ্ন।
রুহোলা

@রুহোলা আপনাকে ধন্যবাদ আমি একটি কোড পর্যালোচনা খুঁজছি না, তবে আরও ভাল একটি অ্যালগরিদম।
কলা

2
বিটিডব্লিউ, আপনি কি নিশ্চিত যে 2.5 মিলিয়ন পূর্ণসংখ্যার উপাদান অ্যারে আপনার স্ট্যাকের সাথে খাপ খায়?
রুহোলা

1
@রুহোলা যে অ্যারেটি ফাইল-স্কোপে রয়েছে তাই এটি স্ট্যাকের উপর চাপানো হয়নি তবে বাইনারি ফাইলের পৃথক বিভাগে রাখা হয়েছে। তবে হ্যাঁ এর মতো বিশাল 2D অ্যারে ঘোষণা করা ভাল ধারণা নয়। একটি ছোট ভেক্টর ক্যাশে
লোকেশনের

1
পরীক্ষার জেনারেটরটির সময় নির্ধারণের সময়টি এখানে রয়েছে : আদর্শ one.com/pDhxS6 এবং এখানে 3.54s, 420 এমবি: আদর্শ one.com/EIrhnR
ברקן

উত্তর:


5

লগ ফ্যাক্টরটি পাওয়ার একটি উপায় এখানে। আমরা dp[i][j]যদি স্ট্রিংগুলিতে পৌঁছাতে পারি তবে সত্য হতে দিন s[i..j]। তারপর:

dp[0][length(s)-1] ->
  true

dp[0][j] ->
  if s[0] != s[j+1]:
    false
  else:
    true if any dp[0][k]
      for j < k  (j + longestMatchRight[0][j+1])

  (The longest match we can use is
   also bound by the current range.)

(Initialise left side similarly.)

এখন বাইরে থেকে পুনরাবৃত্তি করুন এতে:

for i = 1 to length(s)-2:
  for j = length(s)-2 to i:
    dp[i][j] ->
      // We removed on the right
      if s[i] != s[j+1]:
        false
      else:
        true if any dp[i][k]
          for j < k  (j + longestMatchRight[i][j+1])

      // We removed on the left
      if s[i-1] != s[j]:
        true if dp[i][j]
      else:
        true if any dp[k][j]
          for (i - longestMatchLeft[i-1][j])  k < i

আমরা একে শুরু যুগল জন্য দীর্ঘতম ম্যাচ precompute করতে (i, j)মধ্যে O(n^2)পুনরাবৃত্তি সঙ্গে,

longest(i, j) -> 
  if s[i] == s[j]:
    return 1 + longest(i + 1, j + 1)
  else:
    return 0

এটি আমাদের সূচীকরণের সূচনায় iএবং jইন স্ট্রিংয়ের ম্যাচ যাচাইয়ের অনুমতি দেয় O(1)। (আমাদের দুটি ডান এবং বাম দিক প্রয়োজন need)

কিভাবে লগ ফ্যাক্টর পাবেন

আমরা কোনও ডেটা কাঠামো নিয়ে আসার এমন একটি উপায় সম্পর্কে ভাবতে পারি যা আমাদের যদি নির্ধারণ করতে দেয়

any dp[i][k]
  for j < k  (j + longestMatchRight[i][j+1])

(And similarly for the left side.)

ইন O(log n), বিবেচনা করে আমরা ইতিমধ্যে সেই মানগুলি দেখেছি।

এখানে সেগমেন্ট গাছের সাথে সি ++ কোড রয়েছে (ডান এবং বাম প্রশ্নের জন্য, তাই O(n^2 * log n)) এতে বনাননের পরীক্ষার জেনারেটর অন্তর্ভুক্ত রয়েছে। 5000 "এ" অক্ষরের জন্য এটি 3.54s, 420 এমবি ( https://ideone.com/EIrhnR ) এ চলেছিল । স্মৃতি হ্রাস করার জন্য, সেগমেন্টের একটি গাছ একটি একক সারিতে প্রয়োগ করা হয়েছে (আরও স্মৃতি আরও কমিয়ে আনতে আমার এখনও বাম পাশের অনুসন্ধানগুলি দিয়ে তদন্ত করা দরকার।)

#include <iostream>
#include <string>
#include <ctime>
#include <random>
#include <algorithm>    // std::min

using namespace std;

const int MAX_N = 5000;

int seg[2 * MAX_N];
int segsL[MAX_N][2 * MAX_N];
int m[MAX_N][MAX_N][2];
int dp[MAX_N][MAX_N];
int best;

// Adapted from https://codeforces.com/blog/entry/18051
void update(int n, int p, int value) { // set value at position p
  for (seg[p += n] = value; p > 1; p >>= 1)
    seg[p >> 1] = seg[p] + seg[p ^ 1];
}
// Adapted from https://codeforces.com/blog/entry/18051
int query(int n, int l, int r) { // sum on interval [l, r)
  int res = 0;
  for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
    if (l & 1) res += seg[l++];
    if (r & 1) res += seg[--r];
  }
  return res;
}
// Adapted from https://codeforces.com/blog/entry/18051
void updateL(int n, int i, int p, int value) { // set value at position p
  for (segsL[i][p += n] = value; p > 1; p >>= 1)
    segsL[i][p >> 1] = segsL[i][p] + segsL[i][p ^ 1];
}
// Adapted from https://codeforces.com/blog/entry/18051
int queryL(int n, int i, int l, int r) { // sum on interval [l, r)
  int res = 0;
  for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
    if (l & 1) res += segsL[i][l++];
    if (r & 1) res += segsL[i][--r];
  }
  return res;
}

// Code by גלעד ברקן
void precalc(int n, string & s) {
  int i, j;
  for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++) {
      // [longest match left, longest match right]
      m[i][j][0] = (s[i] == s[j]) & 1;
      m[i][j][1] = (s[i] == s[j]) & 1;
    }
  }

  for (i = n - 2; i >= 0; i--)
    for (j = n - 2; j >= 0; j--)
      m[i][j][1] = s[i] == s[j] ? 1 + m[i + 1][j + 1][1] : 0;

  for (i = 1; i < n; i++)
    for (j = 1; j < n; j++)
      m[i][j][0] = s[i] == s[j] ? 1 + m[i - 1][j - 1][0] : 0;
}

// Code by גלעד ברקן
void f(int n, string & s) {
  int i, j, k, longest;

  dp[0][n - 1] = 1;
  update(n, n - 1, 1);
  updateL(n, n - 1, 0, 1);

  // Right side initialisation
  for (j = n - 2; j >= 0; j--) {
    if (s[0] == s[j + 1]) {
      longest = std::min(j + 1, m[0][j + 1][1]);
      for (k = j + 1; k <= j + longest; k++)
        dp[0][j] |= dp[0][k];
      if (dp[0][j]) {
        update(n, j, 1);
        updateL(n, j, 0, 1);
        best = std::min(best, j + 1);
      }
    }
  }

  // Left side initialisation
  for (i = 1; i < n; i++) {
    if (s[i - 1] == s[n - 1]) {
      // We are bound by the current range
      longest = std::min(n - i, m[i - 1][n - 1][0]);
      for (k = i - 1; k >= i - longest; k--)
        dp[i][n - 1] |= dp[k][n - 1];
      if (dp[i][n - 1]) {
        updateL(n, n - 1, i, 1);
        best = std::min(best, n - i);
      }
    }
  }

  for (i = 1; i <= n - 2; i++) {
    for (int ii = 0; ii < MAX_N; ii++) {
      seg[ii * 2] = 0;
      seg[ii * 2 + 1] = 0;
    }
    update(n, n - 1, dp[i][n - 1]);
    for (j = n - 2; j >= i; j--) {
      // We removed on the right
      if (s[i] == s[j + 1]) {
        // We are bound by half the current range
        longest = std::min(j - i + 1, m[i][j + 1][1]);
        //for (k=j+1; k<=j+longest; k++)
        //dp[i][j] |= dp[i][k];
        if (query(n, j + 1, j + longest + 1)) {
          dp[i][j] = 1;
          update(n, j, 1);
          updateL(n, j, i, 1);
        }
      }
      // We removed on the left
      if (s[i - 1] == s[j]) {
        // We are bound by half the current range
        longest = std::min(j - i + 1, m[i - 1][j][0]);
        //for (k=i-1; k>=i-longest; k--)
        //dp[i][j] |= dp[k][j];
        if (queryL(n, j, i - longest, i)) {
          dp[i][j] = 1;
          updateL(n, j, i, 1);
          update(n, j, 1);
        }
      }
      if (dp[i][j])
        best = std::min(best, j - i + 1);
    }
  }
}

int so(string s) {
  for (int i = 0; i < MAX_N; i++) {
    seg[i * 2] = 0;
    seg[i * 2 + 1] = 0;
    for (int j = 0; j < MAX_N; j++) {
      segsL[i][j * 2] = 0;
      segsL[i][j * 2 + 1] = 0;
      m[i][j][0] = 0;
      m[i][j][1] = 0;
      dp[i][j] = 0;
    }
  }
  int n = s.length();
  best = n;
  precalc(n, s);
  f(n, s);
  return best;
}
// End code by גלעד ברקן

// Code by Bananon  =======================================================================

int result;

int lps[MAX_N][MAX_N];
bool checked[MAX_N][MAX_N];

void check(int start, int length) {
  checked[start][length] = true;
  if (length < result) {
    result = length;
  }
  for (int i = lps[start][length]; i != 0; i = lps[start][i - 1]) {
    int newLength = length - i;
    if (!checked[start][newLength])
      check(start, newLength);
    int newStart = start + i;
    if (!checked[newStart][newLength])
      check(newStart, newLength);
  }
}

int my(string str) {
  int n = str.length();
  for (int l = 0; l < n; l++) {
    int subLength = n - l;
    lps[l][0] = 0;
    checked[l][0] = false;
    for (int i = 1; i < subLength; ++i) {
      int j = lps[l][i - 1];
      while (j > 0 && str[i + l] != str[j + l])
        j = lps[l][j - 1];
      if (str[i + l] == str[j + l]) j++;
      lps[l][i] = j;
      checked[l][i] = false;
    }
  }
  result = n - 1;
  check(0, n - 1);
  return result + 1;
}

// generate =================================================================

bool rndBool() {
  return rand() % 2 == 0;
}

int rnd(int bound) {
  return rand() % bound;
}

void untrim(string & str) {
  int length = rnd(str.length());
  int prefixLength = rnd(str.length()) + 1;
  if (rndBool())
    str.append(str.substr(0, prefixLength));
  else {
    string newStr = str.substr(str.length() - prefixLength, prefixLength);
    newStr.append(str);
    str = newStr;
  }
}

void rndTest(int minTestLength, string s) {
  while (s.length() < minTestLength)
    untrim(s);
  int myAns = my(s);
  int soAns = so(s);
  cout << myAns << " " << soAns << '\n';
  if (soAns != myAns) {
    cout << s;
    exit(0);
  }
}

int main() {
  int minTestLength;
  cin >> minTestLength;
  string seed;
  cin >> seed;
  while (true)
    rndTest(minTestLength, seed);
}

এবং এখানে জাভাস্ক্রিপ্ট কোড (লগ ফ্যাক্টর উন্নতি ছাড়াই) পুনরাবৃত্তিটি কাজ করে তা দেখানোর জন্য। (লগ ফ্যাক্টরটি পেতে, আমরা kএকটি একক পরিসরের ক্যোয়ারির সাথে অভ্যন্তরীণ লুপগুলি প্রতিস্থাপন করি ))

debug = 1

function precalc(s){
  let m = new Array(s.length)
  for (let i=0; i<s.length; i++){
    m[i] = new Array(s.length)
    for (let j=0; j<s.length; j++){
      // [longest match left, longest match right]
      m[i][j] = [(s[i] == s[j]) & 1, (s[i] == s[j]) & 1]
    }
  }
  
  for (let i=s.length-2; i>=0; i--)
    for (let j=s.length-2; j>=0; j--)
      m[i][j][1] = s[i] == s[j] ? 1 + m[i+1][j+1][1] : 0

  for (let i=1; i<s.length; i++)
    for (let j=1; j<s.length; j++)
      m[i][j][0] = s[i] == s[j] ? 1 + m[i-1][j-1][0] : 0
  
  return m
}

function f(s){
  m = precalc(s)
  let n = s.length
  let min = s.length
  let dp = new Array(s.length)

  for (let i=0; i<s.length; i++)
    dp[i] = new Array(s.length).fill(0)

  dp[0][s.length-1] = 1
      
  // Right side initialisation
  for (let j=s.length-2; j>=0; j--){
    if (s[0] == s[j+1]){
      let longest = Math.min(j + 1, m[0][j+1][1])
      for (let k=j+1; k<=j+longest; k++)
        dp[0][j] |= dp[0][k]
      if (dp[0][j])
        min = Math.min(min, j + 1)
    }
  }

  // Left side initialisation
  for (let i=1; i<s.length; i++){
    if (s[i-1] == s[s.length-1]){
      let longest = Math.min(s.length - i, m[i-1][s.length-1][0])
      for (let k=i-1; k>=i-longest; k--)
        dp[i][s.length-1] |= dp[k][s.length-1]
      if (dp[i][s.length-1])
        min = Math.min(min, s.length - i)
    }
  }

  for (let i=1; i<=s.length-2; i++){
    for (let j=s.length-2; j>=i; j--){
      // We removed on the right
      if (s[i] == s[j+1]){
        // We are bound by half the current range
        let longest = Math.min(j - i + 1, m[i][j+1][1])
        for (let k=j+1; k<=j+longest; k++)
          dp[i][j] |= dp[i][k]
      }
      // We removed on the left
      if (s[i-1] == s[j]){
        // We are bound by half the current range
        let longest = Math.min(j - i + 1, m[i-1][j][0])
        for (let k=i-1; k>=i-longest; k--)
          dp[i][j] |= dp[k][j]
      }
      if (dp[i][j])
        min = Math.min(min, j - i + 1)
    }
  }

  if (debug){
    let str = ""
    for (let row of dp)
      str += row + "\n"
    console.log(str)
  }

  return min
}

function main(s){
  var strs = [
    "caaca",
    "bbabbbba",
    "baabbabaa",
    "bbabbba",
    "bbbabbbbba",
    "abbabaabbab",
    "abbabaabbaba",
    "aabaabaaabaab",
    "bbabbabbb"
  ]

  for (let s of strs){
    let t = new Date
    console.log(s)
    console.log(f(s))
    //console.log((new Date - t)/1000)
    console.log("")
  }
}

main()


মন্তব্যগুলি বর্ধিত আলোচনার জন্য নয়; এই কথোপকথন চ্যাটে সরানো হয়েছে ।
স্যামুয়েল লিউ

i99 লাইন 99 লাইন থেকে ছায়াছবি আমার মাথা পেতে খুব কঠিন - এটি ইচ্ছাকৃত? 98 & 99 লুপ ঘোষণা ছেড়ে করার জন্য উপস্থিত হয় iMAX_Nলাইন 98 লুপ সুযোগ বাকি জন্য? (সি ++ সংস্করণ)
ডেভিড সি র্যাঙ্কিন

@ ডেভিডসি.র্যাঙ্কিন iকেবলমাত্র সেই চার-লাইন লুপের সুযোগের জন্য ছিল তবে এটি বিভ্রান্তিকর লাগতে পারে। এটি নির্দেশ করার জন্য ধন্যবাদ - আমি এটি পরিবর্তন করেছি, যদিও পরিবর্তনটি কোড প্রয়োগের ক্ষেত্রে প্রভাব ফেলবে না।
ברקן

আমি একটি মধ্যম আউট রিকার্সিভ পদ্ধতির চেষ্টা করেছি, প্রতিশ্রুতি দেখিয়েছি, তবে যখন সমান উপসর্গ / প্রত্যয়টি বড় হয়, তখন নূন্যতম শব্দটি কোন পথে নিয়ে যায় তা নির্ধারণ করার জন্য পুনরাবৃত্তাকার শাখাটি খুব তাড়াতাড়ি হয়ে যায় - দ্রুত।
ডেভিড সি র্যাঙ্কিন

@ ডেভিডসি.রানকিন হ্যাঁ, আমি এটি চেষ্টা করেছিলাম তবে ইতিমধ্যে পরিদর্শন করা রেঞ্জগুলির চেকগুলিও অনেকগুলি প্রমাণিত বলে মনে হয়েছে।
ברקן
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.