Помогите решить проблему с ДП.

Я пытаюсь решить проблему из SPOJ ( ссылка ), который можно кратко описать так: учитывая n интервалов, каждый с целым числом начала и конца, и учитывая максимальное время окончания (назовем его max_end ), найдите, сколькими способами вы можете выбрать набор интервалов, который покрывает 1...макс_конец. Интервалы могут пересекаться. Я попробовал DP; сначала отсортируйте по времени окончания, затем dp[i] — это пара, где dp[i].first — это минимальное количество интервалов, необходимых для покрытия 1...end[i] последний с использованием интервала i а dp[ i ].second — количество способов сделать это. Вот мой основной цикл DP:

for( int i = 1; i < n; i ++ ) {
    for( int j = 0; j < i; j ++ ) {
        if( ! ( x[ j ].end >= x[ i ].start - 1 ) )
            continue;
        if( dp[ j ].first + 1 < dp[ i ].first ) {
            dp[ i ].first = dp[ j ].first + 1;
            dp[ i ].second = dp[ j ].second;
        }
        else if( dp[ j ].first + 1 == dp[ i ].first ) {
            dp[ i ].second += dp[ j ].second;
        }
    }
}

К сожалению, это не сработало. Может кто-нибудь, пожалуйста, скажите мне, где у меня ошибка? Заранее спасибо! :)


person Chris    schedule 22.08.2011    source источник
comment
Каковы ваши начальные значения для dp[0]? Кроме того, в связанном вопросе конкретно запрашивается количество минимальных интервалов, охватывающих определенный день; Вы уверены, что этот алгоритм гарантирует это?   -  person templatetypedef    schedule 22.08.2011
comment
Привет, я инициализирую dp[0].first = dp[0].second = 1, если start[0] = 1. И я думаю, что мой алгоритм гарантирует это. Если у вас есть какое-то решение, я был бы признателен:)   -  person Chris    schedule 22.08.2011
comment
Не могли бы вы рассказать о своей интуиции здесь? Я не могу понять, почему ваш код работает.   -  person templatetypedef    schedule 22.08.2011
comment
Я не понимаю, как этот алгоритм O (N ^ 2) решит очевидную проблему O (2 ^ N)   -  person n. 1.8e9-where's-my-share m.    schedule 22.08.2011
comment
Предположим, вы хотите вычислить dp[i] предположим, что вы вычислили все dp[1..i - 1]. Для каждого j в 1..i - 1: если вы можете покрыть до конца интервала j, а интервал i будет дополнять остальную часть end[j]+1 .. end[i], то обновите dp[i] так что вы сбрасываете количество способов, если выбор j лучше. Если выбор j приведет к тому же количеству используемых интервалов, то вы увеличиваете количество способов для интервала i.   -  person Chris    schedule 22.08.2011
comment
Это точно не O( 2 ^ n ) :D N может достигать 100, и я думаю, что 2 ^ 100 - это слишком много :D   -  person Chris    schedule 22.08.2011


Ответы (1)


Я не уверен, что понимаю вашу идею решения, но я описываю свое решение AC:

Я использую функцию с запоминанием, но вы можете переписать ее с помощью нерекурсивного DP.

Допустим, у нас есть наши интервалы в массиве

пара а[100]; где a[i].first — начало интервала, а a[i].second — конец интервала.

Сначала отсортируйте этот массив по begin (поведение алгоритма сортировки stl по умолчанию с компаратором пар по умолчанию).

Теперь представьте, что мы «ставим» интервалы один за другим от начала до конца.

пусть f(int x, int prev) возвращает количество способов закончить заполнение, если в настоящее время последний интервал равен x, а предыдущий равен «prev».

мы рассчитаем его следующим образом:

int f(int x, int prev) {
  // if already calculated dp[x][prev], return it. Otherwise, calculate it
  if (dp[x][prev] != -1) {
    return dp[x][prev];
  }
  if (a[x].second == m) {
    return dp[x][prev] = 1; // it means - X is last interval in day
  }
  else {
    dp[x][prev] = 0;
    for (int i = x + 1; i < n; ++i) { // try to select next interval
      if (a[i].first <= a[x].second && // there must be not empty space after x interval
          a[i].second > a[x].second && // if this is false, the set won't be minimal - i interval is useless
          a[i].first > a[x].first && // if this is false, the set won't be minimal, x interval is useless
          a[prev].second < a[i].first) { // if this is false, the set won't be minimal, x interval is useless.  
        dp[x][prev] = (dp[x][prev] + f(i, x)) % 100000000;
      }
    }
  }
  return dp[x][prev];
}

После этого нам нужно вызвать эту функцию для каждой пары интервалов, первый из которых начинается с 0, а второй связан с первым:

for (int i = 0; i < n; ++i) {
  if (a[i].first == 0) {
     for (int j = i + 1; j < n; ++j) {
        if (a[j].first > 0 && // we don't need to start at 0 - in this case either i or j will be useless
            a[j].first <= a[i].second && // there must be no space after i interval
            a[j].second > a[i].second) { // in opposite case j will be useless
           res = (res + f(j, i)) % 100000000;
        }
     }
     // also we need to check the case when we use only one interval:
     if (a[i].second == m) {
        res = (res + 1) % 100000000;
     }
  }
}

После этого нам нужно только распечатать res.

person Oleksandr Kuvshynov    schedule 21.09.2011
comment
Как он вообще может что-то считать? Вы рассматриваете только значения i, где a[i].first == 0, затем вы рассматриваете только значения j, где a[j].first › 0 и a[j].first ‹= a[i].first. Учитывая первое сравнение, второе является противоречием, и вы никогда не будете вызывать f(j, i) - person Peer Sommerlund; 25.09.2011
comment
упс, ошибся при форматировании кода. Спасибо, отредактировал. - person Oleksandr Kuvshynov; 30.09.2011