Maksimum#4473

Maksimum (Editoryal)

Problem, aralık maksimum sorgularının yanıtlanmasını gerektirmektedir. Dizinin elemanları değişmediğinden, statik bir veri yapısı uygundur. Sorgu sayısı 5 \cdot 10^5'e ulaşmaktadır ve dizinin uzunluğu 10^5'tir. Her segmentin maksimumunu doğrudan hesaplamak 5 \cdot 10^{10}'a kadar işlem gerektirir ki bu çok yavaştır.

RMQ (Range Minimum Query) problemi için sparse table (seyrek tablo) kullanıyoruz, ancak minimum yerine maksimumu arıyoruz (Range Maximum Query). Maksimum da, minimum gibi, bazı elemanlar iki kez sayılırsa değişmez. Bu nedenle, sorgu segmenti 2^k uzunluğunda örtüşen iki segment ile kaplanabilir ve bunların maksimumlarının büyüğü seçilir.

Tablo hücresi, maksimum elemanın indeksini veya maksimumun kendisini saklayabilir. Problem yalnızca değerin yazdırılmasını gerektirdiğinden, maksimumu saklamak yeterlidir. İndeks varyantı, maksimumun konumunun bilinmesi gerektiğinde ihtiyaç duyulur. Her iki uygulama da aşağıda verilmiştir.

Problem ifadesine göre, 1 \le l, r \le n'dir ve l \le r olduğu garanti edilmemektedir. Bu nedenle, bir sorguyu yürütmeden önce, gerekirse sınırları değiştiriyoruz.

Karmaşıklık. Sparse table'ın oluşturulması O(n \log n) sürer ve her sorgu sürer. Genel karmaşıklık O(n \log n + q)'dur.

Örnek

Örnekteki diziyi ele alalım: (5, 1, 2, 8, 7, 6, 10, 7, 5, 6). mas[i][j]'nin [i; i + 2^j - 1] segmenti üzerindeki maksimum olduğu tablosu aşağıda gösterilmiştir. Tire işareti, segmentin dizinin dışına çıktığını gösterir.

\boldsymbol{j \setminus i}

—

—

—

—

—

—

—

—

—

—

—

Örneğin, mas[4][2] = \max(mas[4][1], mas[6][1]) = \max(8, 10) = 10 — bu, segmenti üzerindeki maksimumdur.

sorgusunu ele alalım. Segmentin uzunluğu 'tir ve 'i aşmayan en büyük ikinin kuvveti 'tür, yani k = 2. segmenti, segmentinde örtüşen ve segmentleri ile kaplanır:

Yanıt \max(mas[2][2], mas[3][2]) = \max(8, 8) = 8'dir. elemanı her iki segmente de aittir, ancak bu maksimum değerini etkilemez.

Örnekten alınan tüm sorgular tabloda listelenmiştir:

sorgu

segmentler

maksimumlar

yanıt

,

,

,

,

,

,

,

,

ve uzunluğundaki segmentler için (, ve sorguları), kaplayan her iki segment de sorgu segmentinin kendisiyle çakışmaktadır.

Algoritma uygulaması — maksimumun indeksi

Giriş dizisi a_i (n \le 10^5), dizisinde indeksinden başlayarak saklanır. Bu varyantta, mas[i][j] hücresi [i; i + 2^j - 1] segmenti üzerindeki maksimum elemanın indeksini saklar ve maksimumun kendisi a[mas[i][j]]'ye eşittir. 2^{16} \le 10^5 < 2^{17} olduğundan, ikinci boyut için LOGMAX = 17 yeterlidir.

#define MAX 100010
#define LOGMAX 17
int mas[MAX][LOGMAX];
int a[MAX];

Build_RMQ_Array fonksiyonu, indekslerin sparse table'ını oluşturur.

void Build_RMQ_Array()
{

j = 0 için, segment tek bir elemandan oluşur, bu nedenle mas[i][0] = i'dir.

  for (int i = 1; i <= n; i++) mas[i][0] = i;

j > 0 için, dizisinin mas[i][j - 1] ve mas[i + 2^{j-1}][j - 1] indeksli elemanlarını — segmentin iki yarısındaki maksimumları — karşılaştırın ve büyük olanın indeksini saklayın.

  for (int j = 1; (1 << j) <= n; j++)
    for (int i = 1; i + (1 << j) - 1 <= n; i++)
      if (a[mas[i][j - 1]] > a[mas[i + (1 << (j - 1))][j - 1]])
        mas[i][j] = mas[i][j - 1];
      else mas[i][j] = mas[i + (1 << (j - 1))][j - 1];
}

RangeMaxQuery fonksiyonu, segmenti üzerindeki maksimum elemanın indeksini döndürür.

int RangeMaxQuery(int i, int j)
{
  int temp, k = 0;

i > j ise, sınırlar değiştirilir.

  if (i > j) temp = i, i = j, j = temp;

Akabinde, bir döngüde, 2^k \le j - i + 1 olacak şekilde en büyük 'yı bulun.

  while ((1 << (k + 1)) <= j - i + 1) k++;

mas[i][k] ve mas[j - 2^k + 1][k] indeksli elemanları karşılaştırın ve büyük olanının indeksini döndürün.

  return (a[mas[i][k]] >= a[mas[j - (1 << k) + 1][k]]) ?
         mas[i][k] : mas[j - (1 << k) + 1][k];
}

Programın ana bölümü. Dizinin boyutu 'yi ve elemanlarını, indeksinden başlayarak okuyun.

scanf("%d", &n);
for (i = 1; i <= n; i++)
  scanf("%d", &a[i]);

dizisinden sparse table'ını oluşturun.

Build_RMQ_Array();

Sorgu sayısı 'yu okuyun.

scanf("%d", &q);

Her sorgu için, RangeMaxQuery fonksiyonu segmenti üzerindeki maksimum elemanın indeksini döndürür ve biz elemanının kendisini yazdırırız.

for (i = 0; i < q; i++)
{
  scanf("%d %d", &u, &v);
  int index = RangeMaxQuery(u, v);
  printf("%d\n", a[index]);
}

Algoritma uygulaması — maksimum

Giriş dizisi a_i (n \le 10^5), dizisinde indeksinden başlayarak saklanır. Bu varyantta, mas[i][j] hücresi [i; i + 2^j - 1] segmenti üzerindeki maksimumun kendisini saklar. 2^{16} \le 10^5 < 2^{17} olduğundan, ikinci boyut için LOGMAX = 17 yeterlidir.

#define MAX 100010
#define LOGMAX 17
int mas[MAX][LOGMAX];
int a[MAX];

Build_RMQ_Array fonksiyonu, dizisinden sparse table'ı oluşturur:

mas[i][j] = \max(b[i], b[i + 1], \ldots, b[i + 2^j - 1])
void Build_RMQ_Array(int *b)
{

j = 0 için, sparse table orijinal dizi elemanlarını içerir: mas[i][0] = b[i].

  for (int i = 1; i <= n; i++) mas[i][0] = b[i];

j > 0 için, mas[i][j] değeri, segmentin iki yarısındaki maksimumların büyüğüne eşittir.

  for (int j = 1; 1 << j <= n; j++)
    for (int i = 1; i + (1 << j) - 1 <= n; i++)
      mas[i][j] = max(mas[i][j - 1], mas[i + (1 << (j - 1))][j - 1]);
}

RangeMaxQuery fonksiyonu, segmenti üzerindeki maksimumu döndürür.

int RangeMaxQuery(int i, int j)
{
  int temp, k = 0;

i > j ise, sınırlar değiştirilir.

  if (i > j) temp = i, i = j, j = temp;

Bir döngüde, 2^k \le j - i + 1 olacak şekilde en büyük 'yı bulun. Bu hesaplama yöntemi logaritma fonksiyonu gerektirmez ve kayan nokta hatalarına bağlı değildir.

  while ((1 << (k + 1)) <= j - i + 1) k++;

Yanıt, [i; i + 2^k - 1] ve [j - 2^k + 1; j] segmentleri üzerindeki maksimumların büyüğüdür.

  return max(mas[i][k],mas[j - (1<<k) + 1][k]);
}

Programın ana bölümü. Dizinin boyutu 'yi ve elemanlarını, indeksinden başlayarak okuyun.

scanf("%d",&n);
for(i = 1; i <= n; i++) scanf("%d",&a[i]);

dizisinden sparse table'ını oluşturun.

Build_RMQ_Array(a);

Sorgu sayısı 'yu okuyun. Her sorgu için, segmenti üzerindeki maksimumu yazdırın.

scanf("%d",&q);
for(i = 0; i < q; i++)
{
  scanf("%d %d",&u,&v);
  printf("%d\n",RangeMaxQuery(u,v));
}