Problem, bir dizinin, üretildikten sonra değişmeyen bir segmenti üzerinde tekrar tekrar minimum bulmayı gerektirir. Sorgu sayısı 10^7'ye ulaştığından, her sorgu zamanda işlenmelidir. Bunu yapmak için sparse table (seyrek tablo) kullanırız — RMQ (Range Minimum Query) problemi için statik bir veri yapısı.
Tablonun oluşturulması. Her konumu ve i + 2^j - 1 \le n koşulunu sağlayan her için, mas[i][j] hücresi, uzunluğu 2^j olan [i; i + 2^j - 1] segmentindeki minimum elemanın indeksini tutar. j = 0 için segment tek bir elemandan oluşur, dolayısıyla mas[i][0] = i.
j > 0 için, uzunluğu 2^j olan segmenti uzunluğu 2^{j-1} olan iki yarıya bölelim: [i; i + 2^{j-1} - 1] ve [i + 2^{j-1}; i + 2^j - 1]. Yarılardaki minimumların indeksleri zaten hesaplanmıştır — bunlar mas[i][j - 1] ve mas[i + 2^{j-1}][j - 1]'dir:
Bütün segmentin minimumu, iki yarının minimumlarından küçük olanına eşittir. Bu nedenle, mas[i][j], mas[i][j - 1] ve mas[i + 2^{j-1}][j - 1] indekslerinden hangisi daha küçük dizi elemanını gösteriyorsa ona eşittir. Tablo, artan sırasına göre doldurulur ve O(n \log n) hücre içerir.
Bir sorgunun yanıtlanması. segmenti üzerinde minimumu bulmamız gerektiğini varsayalım. k = \lfloor \log_2(v - u + 1) \rfloor, segmentin uzunluğunu aşmayan en büyük ikinin kuvvetinin üssü olsun. 'dan başlayan uzunluğu 2^k olan segment ile 'de biten uzunluğu 2^k olan segment birlikte 'yi tamamen kapsar, çünkü 2 \cdot 2^k > v - u + 1:
Bu segmentler çakışabilir, ancak bu minimum için önemli değildir: iki kez sayılan bir eleman sonucu değiştirmez (örneğin toplamın aksine). Bu nedenle, üzerindeki minimum, a[mas[u][k]] ve a[mas[v - 2^k + 1][k]] elemanlarından küçük olanına eşittir ve bir sorgu zamanda işlenir.
Üretilen sorguların ve sınırları herhangi bir sırada gelebilir, bu nedenle bir sorguyu çalıştırmadan önce gerekirse onları değiştiririz (swap).
Karmaşıklık. Tablonun oluşturulması O(n \log n) sürer ve her sorgu sürer. Genel karmaşıklık O(n \log n + m)'dir. m \le 10^7 ile, zaman limitine sığmayı mümkün kılan tam olarak sorgu süresidir.
Örnek
Şu örneği ele alalım: n = 10, m = 8, a_1 = 12345. Üretilen dizi, problem metninde verilen dizi ile örtüşmektedir. Bu dizi için tablosu aşağıda gösterilmiştir. Hücreler minimum elemanların indekslerini içerir; bir tire, segmentin dizinin dışına çıktığı anlamına gelir.
\boldsymbol{j \setminus i} | ||||||||||
— | ||||||||||
— | — | — | ||||||||
— | — | — | — | — | — | — |
Örneğin, mas[3][2] = 5: segmentinde, minimum eleman a_5 = 1565158'dir. mas[2][3] = 2 değeri, mas[2][2] = 2 ve mas[6][2] = 8'den elde edilir, çünkü a_2 = 305498 < a_8 = 570265'tir.
İlk sorgu: u_1 = 3, v_1 = 9. Segmentin uzunluğu 'dir, dolayısıyla k = 2. segmenti, mas[3][2] = 5 ve mas[6][2] = 8 olan ve segmentleri tarafından kapsanır. a_8 = 570265 < a_5 = 1565158 olduğundan, cevap ans_1 = 570265'tir.
İkinci sorgunun parametreleri:
u_2 > v_2 olduğundan, sınırlar değiştirilir ve segmenti üzerindeki minimum hesaplanır.
Tüm sorgular tabloda listelenmiştir:
\boldsymbol{u_i} | \boldsymbol{v_i} | segment | \boldsymbol{ans_i} | |
Son sorgunun parametreleri u_8 = 5, v_8 = 3'tür ve cevabı ans_8 = 1565158'dir. Yazdırılan değerler tam olarak bunlardır. Dikkat edilmesi gereken nokta, segmentin sıralanmış sınırlarının değil, orijinal u_8 ve v_8 değerlerinin yazdırılmasıdır.
Algoritmanın gerçekleştirilmesi
dizisinin elemanları (n \le 10^5) indeksinden başlayarak saklanır. Sparse table, dizisinde saklanır: mas[i][j], [i; i + 2^j - 1] segmentindeki minimum elemanın indeksidir. 2^{16} \le 10^5 < 2^{17} olduğundan, değerleri 'yı aşmaz, dolayısıyla ikinci boyut için LOGMAX = 17 yeterlidir.
#define MAX 100001
#define LOGMAX 17
int a[MAX];
int mas[MAX][LOGMAX];check fonksiyonu, u \le v olacak şekilde u > v ise ve değerlerini değiştirir. Parametreler referans olarak geçirilir.
void check(int &u, int &v)
{
int temp;
if (u > v) temp = u, u = v, v = temp;
}RMQ_nlogn fonksiyonu sparse table'ı oluşturur.
void RMQ_nlogn(void)
{
int i, j;Önce, tüm değerleri için mas[i][0] = i olarak ayarlanır.
for (i = 1; i <= n; i++) mas[i][0] = i;Sonra, 2^j \le n olduğu sürece her için ve [i; i + 2^j - 1] segmenti dizinin dışına çıkmadığı sürece her için, mas[i][j - 1] ve mas[i + 2^{j-1}][j - 1] arasından daha küçük elemanın indeksi seçilir.
for (j = 1; (1 << j) <= n; j++)
for (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];
}q fonksiyonu, segmentindeki minimum elemanın indeksini döndürür. k = \lfloor \log_2(j - i + 1) \rfloor hesaplanır ve uzunluğu 2^k olan iki segmentin minimumları karşılaştırılır: [i; i + 2^k - 1] ve [j - 2^k + 1; j].
int q(int i, int j)
{
int k = (int)(log(1.0*j - i + 1) / log(2.0));
int res = mas[i][k];
if (a[mas[j - (1<<k) + 1][k]] < a[res])
res = mas[j - (1<<k) + 1][k];
return res;
}Programın ana kısmı. , , ilk dizi elemanı a_1 ve ilk sorgunun ve parametreleri okunur.
scanf("%d %d",&n,&m);
scanf("%d %d %d",&a[1],&u,&v);a_2, a_3, \ldots, a_n elemanları, problem metnindeki formül kullanılarak üretilir. 23 \cdot a_i çarpımı 23 \cdot 16714589 \approx 3.8 \cdot 10^8'i aşmaz ve tipine sığar.
for(i = 2; i <= n; i++)
a[i] = (23 * a[i-1] + 21563) % 16714589;Sparse table oluşturulur.
RMQ_nlogn();sorgu teker teker işlenir.
for (i = 1; i <= m; i++)
{Mevcut sorgunun sınırları ve 'e kopyalanır ve gerekirse check fonksiyonu ile sıralanır. ve değerlerinin kendisi değiştirilmez: bir sonraki sorgunun parametreleri bunlardan hesaplanır ve cevapta yazdırılanlar da bunlardır.
u1 = u; v1 = v; check(u1,v1);[u1; v1] segmentindeki minimum eleman bulunur.
ans = a[q(u1,v1)];Eğer sorgu son sorgu değilse, bir sonraki sorgunun parametreleri problem metnindeki formüller kullanılarak hesaplanır.
if (i < m)
{
u = ((17*u + 751 + ans + 2*i) % n) + 1;
v = ((13*v + 593 + ans + 5*i) % n) + 1;
}
}Son sorgunun parametreleri ve cevabı yazdırılır.
printf("%d %d %d\n",u,v,ans);