Nano Hash - криптовалюты, майнинг, программирование

Динамическое программирование находит количество всех возрастающих подпоследовательностей, оканчивающихся на Xj в заданной последовательности для каждого индекса j

Дана последовательность действительных чисел (X1, X2, ..., Xn). Напишите максимально эффективный алгоритм, который находит количество строго возрастающих подпоследовательностей для каждого индекса j, оканчивающихся на Xj.

(Определяется строго возрастающая подпоследовательность: Xa1, Xa2, ..,Xai, когда a1 ‹ a2 .. ‹ ai, сохраняется: Xa1 ‹ Xa2 ‹ .. ‹ Xai. подпоследовательность заканчивается на Xj, если ai = j)

Мое решение должно включать формулу повторения, которая решает эту проблему в O(n^2), и доказательство правильности, я смог решить ее только с помощью вложенного цикла for, и я не уверен, есть ли рекурсивное решение O(n^2).

List a[1…n] <- [1…1]
 For j= 1 to n
    For i= 1 to j-1
       If xi<xj then
          a[i]= a[j]+a[i]; 

  • Чего ты хочешь тогда? 03.07.2015
  • рекурсия/запоминание стиля ответа 03.07.2015
  • Извините, вы не можете просто испортить свой пост. Вопрос плюс ответ — это совместная работа под лицензией CC wiki. 05.07.2015
  • Этот парень отозвал свое согласие на мое решение после того, как обнаружил, что не может удалить этот вопрос... лол 07.07.2015

Ответы:


1

Итак, в основном вы хотите использовать подход «сверху вниз»? Как насчет этого?

#include<bits/stdc++.h>
using namespace std;

int dp[15] = {0};
int a[15] = {3,2,1,14,2,4,5,9,7,20,12,13,6,8,11};

int DP(int x){
	if(x == 0) return 1;
	if(dp[x]) return dp[x];
	int ret = 1;
	for(int i=0; i<x; i++) if(a[x] > a[i]) ret += DP(i);
	return dp[x] = ret;
}
int main(){
	printf("%d\n", DP(14));
	return 0;	
}

Формула повторения одна и та же, она не зависит от того, как вы реализуете решение (сверху вниз или снизу вверх).

Вот:

введите здесь описание изображения

03.07.2015
  • не могли бы вы уточнить? если(dp[x]) вернуть dp[x]; зачем это нужно? 03.07.2015
  • И это работает на O (N ^ 2)? 03.07.2015
  • @TCP_Explorer if(dp[x]) return dp[x] — это то, что препятствует тому, чтобы это выполнялось в экспоненциальное время, пересчитывая DP(i) каждый раз, когда нам это нужно для вычисления DP(i+1), DP(i+2) и т. д. Эта строка говорит, что если мы уже вычислили dp[x], просто найдите его в таблице и верните. . Кроме того, это выполняется за O(n^2), так как для каждого x нам нужно суммировать значения для каждого i<x. 03.07.2015
  • @TCP_Explorer Я думал, вы знаете, что такое запоминание ... Для получения подробной информации вы можете обратиться к объяснению Джеймса выше ... 03.07.2015
  • Я знаю, понял! разве вы не должны установить dp[15] = {1}? поскольку каждый элемент является подпоследовательностью. 03.07.2015
  • У меня проблемы с пониманием вашего кода cpp. возврат dp[x] = рет; что это делает, кроме установки ret в dp[x]?. Если я установлю dp[x] = {1}, условие if(dp[x]) всегда будет выдавать True. 03.07.2015
  • так что вы не знаете запоминания... dp[x] будет установлено в true, только если оно вычислено ранее, поэтому верните его значение напрямую, вместо того, чтобы переходить в цикл for и делать массовый вызов его подзадачи... в противном случае это будет экспоненциально Продолжительность 03.07.2015
  • Я имел в виду, что если мы установим dp[15] = {1}, это всегда будет верно, но вы указали ret = 1, что объясняет, что вы сделали, вместо того, чтобы инициализировать его с помощью «1». не могли бы вы объяснить последнее возвращение? возврат dp[x] = рет; это вернет как ret, так и назначит ret последнему индексу? 03.07.2015
  • это СНАЧАЛА присвоит значение ret dp[x], а затем вернет dp[x] как результат функции... обратите внимание, что теперь dp[x] должно быть не менее 1, поэтому все последующие вызовы этой подзадачи могут получить его значение непосредственно в строке if(dp[x]) return dp[x]... 03.07.2015
  • Я сравнил оба поставляемых решения, я думаю, что что-то не так, на входе это массив dp в конце: [1, 0, 2, 6, 12, 24, 24, 0, 0, 0, 24, 72, 168], источник: cpp.sh/6ajs, второе решение возвращает другой вывод для того же ввода. вывод: [4, 2, 1, 18, 2, 10, 20, 43, 41, 147, 127, 254, 40, 121, 285], источник: cpp.sh/6ajs 03.07.2015
  • пожалуйста, проигнорируйте мой последний комментарий 03.07.2015
  • и какая часть заставляет вас не принимать это решение? 03.07.2015
  • принято. Буду признателен, если вы поможете мне с формулой повторения. Спасибо! 04.07.2015

  • 2

    Когда вы делаете a[i] = a[j] + a[i], вы на самом деле просто делаете a[i]++, так как a[j]==1 каждый раз запускаете эту строку. Если это не очевидно сразу, вам следует вручную проследить выполнение вашего кода и посмотреть, что он на самом деле делает.

    Что вы, вероятно, хотели:

    for j in range(n):
        for i in range(j):
            if x[i] < x[j]:
                a[j] += a[i]
    

    Рекуррентное соотношение: количество возрастающих подпоследовательностей, заканчивающихся на j, равно сумме числа возрастающих подпоследовательностей, заканчивающихся на каждый индекс меньше j, плюс 1 для последовательности, содержащей только j.

    Анализ

    Это решение O (n ^ 2). Доказательство: первая итерация внешнего цикла, мы повторяем 1 раз во внутреннем цикле (внутренний цикл выполняет постоянное количество вычислений на каждой итерации). Каждый раз, когда мы увеличиваем j, мы увеличиваем количество итераций во внутреннем цикле. В итоге мы вызываем код внутри внутреннего цикла всего 1 + 2 + 3 + ... (n-1) + n раз. Пусть f(n) = 1 + 2 + 3 + ... n.

    Вспомним определение слова big-O: f(n) = O(n^ 2) означает, что существуют положительные константы c и k, такие что 0 ≤ f(n) ≤ cn^2 для всех n ≥ k. Значения c и k должны быть фиксированными для функции f и не должны зависеть от n.

    Ясно 0 ≤ f(n), поэтому нам просто нужно показать, что существуют некоторые c и k такие, что f(n) ≤ cn^2 для всех n ≥ k. Я выберу c=1 и k=1. Вы можете убедиться, что f(n) <= n^2 для всех n.

    Хотя вы не спрашивали, ваш комментарий ниже говорит о том, что вам нужно доказательство того, что это самая жесткая граница, которую мы можем получить. Так что я докажу, что f(n) = Ω(n^2)

    f(n)=Ω(n^2) если для некоторой константы c>0 и всех достаточно больших n, то f(n) ≥ cn^2.

    Давайте выберем c=4. Затем у нас есть f(n) = 1 + 2 + 3 + ... n >= (n/2) + (n/2)+1 + (n/2)+2 + ... n, где мы просто вычли 1 + 2 + ... (n/2)-1 из f(n), чтобы получить это неравенство.

    Кроме того, (n/2) + (n/2)+1 + (n/2)+2 + ... n >= (n/2)*(n/2), так как мы можем взять все n/2 условия, равные >= n/2, и ограничить их ровно на n/2 каждый.

    Но (n/2)*(n/2) = (n^2)/4, поэтому у нас есть f(n) >= (1/4)n^2 для всех n, поэтому мы показали, что f(n) = Ω(n^2).

    03.07.2015
  • Этот ответ считается стилем динамического программирования? Я пытался решить это с помощью рекурсии. Я не уверен, что вы имели в виду под a[j] += a[i], не могли бы вы объяснить? Спасибо 03.07.2015
  • Это сокращение от a[j] = a[j] + a[i]. 03.07.2015
  • Это определенно считается динамическим программированием, поскольку для решения a[j] мы используем информацию, которую мы уже вычислили для a[j-1], a[j-2] ... a[1]. Есть ли причина, по которой вы должны использовать рекурсию? Обычно при выполнении DP все делается итеративно, поскольку нам нужно сохранять промежуточные результаты. 03.07.2015
  • В чем преимущество этого подхода по сравнению с ОП? Это в основном тот же код с дополнительным синтаксическим сахаром (который существует не во всех языках). 03.07.2015
  • Этот код увеличивает переменную во внешнем цикле, а не во внутреннем цикле. На самом деле решение OP неверно. Для n=2, Input = [1, 2] будет возвращено answer=[2,1]. Но он должен вернуть [1,2]. 03.07.2015
  • Джеймс, не могли бы вы помочь мне с формулой рекуррентности для вашего примера? 03.07.2015
  • Я сравнил оба предоставленных решения, что-то не так в этом решении, так как его вывод: [4, 2, 1, 18, 2, 10, 20, 43, 41, 147, 127, 254, 40, 121, 285], вот фрагмент кода: repl.it/vRL/1, в то время как версия CPP возвращает совершенно другое вывод: cpp.sh/6ajs, [0, 1, 1, 0, 2, 6, 12, 24 , 24, 0, 0, 0, 24, 72, 168] 03.07.2015
  • Проигнорируйте мой последний комментарий, пожалуйста 03.07.2015
  • можно ли сформулировать рекуррентную формулу для этого подхода? 03.07.2015
  • @TCP_Explorer, я объяснил отношение повторения в своем посте. Просто переведите это в формулу. 03.07.2015
  • это O (j ^ 2), а не O (n ^ 2). вы не должны бежать от j в диапазоне n, но в диапазоне j. 06.07.2015
  • @TCP_Explorer, см. обновленный пост об анализе сложности. Это не может быть O (j ^ 2), поскольку j - это переменная, которую я ввел в самом алгоритме... 06.07.2015
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..