Poçt göndərməsi#2509

Poçt göndərməsi (Həll təhlili)

Göndərilməli olan əşyalar alt çoxluğunu təsvir edən maska anlayışını daxil edək. Məsələn, mask = 5 = 101_2 maskası, sıfırıncı və ikinci əşyaları daxil edən alt çoxluğu təyin edir (əşyalar sıfırdan nömrələnir).

Hər bir alt çoxluq üçün onun ümumi çəkisini hesablayın, bu da göndərmə xərclərini təyin edir. Əgər bu çəki tam olaraq gramm-a bərabərdirsə, p \le 1000 olduğu üçün, həmişə xüsusi təklifi istifadə etmək sərfəlidir.

Qoy MinCost[mask] maska ilə təyin olunan əşyalar alt çoxluğunun göndərilməsinin minimal (avro) xərclərini ifadə etsin. O zaman hər hansı maskası alt çoxluğu olduğu halda belə münasibət yerinə yetirilir:

MinCost[mask] = \min_{x \sub mask} (MinCost[mask~xor~x] + MinCost[x])

Maska 2^n - 1, ədədli bütün alqoritmlə uyğun gəlir, beləliklə, məsələnin cavabı MinCost[2^n - 1] qiyməti olacaq.

Teorem. Bütün maskaların və onların alt maskalarının hesablanması O(3^n) vaxtında baş verir.

Sübut 1. -ci biti nəzərə alaq. Onun üçün üç variant mümkündür:

Bütövlükdə bitlərin sayı , deməli kombinasiyaların ümumi sayı 3^n-ə bərabərdir.

Sübut 2. Qoy maskası quraşdırılmış bitlərə sahib olsun. Onda məhz 2^k alt maskalara sahib olacaq. Maskaların uzunluğunun , quraşdırılmış bitlərlə sayda C_n^k-yə bərabərliyini bərabərdir. Sonuç olaraq, kombinasiyaların ümumi sayı

\sum_{k=0}^n C_n^k 2^k = (1 + 2)^n = 3^n

Alqoritmin həyata keçirilməsi

Əşyaların kütlələri massivində saxlanacaq. MinCost[mask] hüceyrəsində maskası ilə təyin olunan əşyalar alt çoxluğunun göndərilməsinin minimal xərcləri saxlanacaq.

int m[15], MinCost[1 << 14];

Nəticə məlumatlarını oxuyuruq.

scanf("%d %d",&n,&p);
for (i = 0; i < n; i++) scanf("%d",&m[i]);

Dəyişəndə , -dan 2^n - 1-ə qədər bütün mümkün maskalara baxırıq.

for (mask = 0; mask < (1 << n); mask++)
{

Dəyişəndə -da maskasına uyğun alt çoxluğun ümumi çəkisini qeyd edəcəyik. Bunun üçün sayının ikilik təqdimatına baxaq: əgər mövqeyində varsa, onda -ci əşyanın çəkisini -a əlavə edəcəyik.

  for (Cost = i = 0; i < n; i++)
    if (mask & (1 << i)) Cost += m[i];

Əgər dəyəri tam olaraq -ə bərabərdirsə, onda p \le 1000 olduğu üçün, dəyişəninə dəyərini təyin edirik.

  if (Cost == 1000) Cost = p;

Hesablanan göndərmə qiymətini massivinin müvafiq hücrəsinə daxil edək.

  MinCost[mask] = Cost;
}

Hər bir maskası üçün x \sub mask olan bütün alt çoxluqları təyin edirik (mask alt çoxluğu yalnız o halda olur ki, mask~AND~x -ə bərabər olur). Əgər maskasına uyğun alt çoxluqdan maskasına uyğun alt çoxluğu çıxsaq, mask~XOR~x maskalı çoxluq alınır.

Məsələn, əgər mask = 1101_2, x = 1001_2, onda mask~XOR~x = 100_2, bu, çoxluğun nəzəri əməliyyatına uyğun gəlir \{1, 3, 4\} \setminus \{1, 4\} = \{3\}.

for (mask = 0; mask < (1 << n); mask++)
  for (x = 0; x < mask; x++)

Tapşırığın təhlilində göstərilən rekursiv əlaqəyə görə MinCost[mask] dəyərini yenidən hesablayırıq.

    if ((mask & x) == x) 
      MinCost[mask] = min(MinCost[mask],MinCost[mask^x] + MinCost[x]);

Cavabı çıxarırıq, bu da MinCost[2^n - 1] hücrəsində yerləşir.

printf("%d\n",MinCost[(1 << n) - 1]);

O(3^n) qiymətləndirməsi ilə həyata keçirilməsi

Konstant və massivlərin elan edilməsi.

#define MAX 0xFFFFFFFF
unsigned int m[15], MinCost[1 << 14];

Mask üçün bütün alt maskalarını -u baxmaq və

FindCost(sub) + FindCost(mask~XOR~sub)

mümkün olan minimum dəyərləri tapmaq lazımdır.

Bu əlavə simmetriya nəzərə alındıqda, yalnız sub \ge mask~XOR~sub şərtinin yerinə yetirildiyi alt maskaları nəzərə almaq kifayətdir.

unsigned int FindCost(int mask)
{
  if (MinCost[mask] != MAX) return MinCost[mask];
  // sub maska maskasının bütün alt maskalarını nəzərdən keçirir
  for (int sub = (mask - 1) & mask; 
           sub >= mask ^ sub; sub = (sub - 1) & mask)
    MinCost[mask] = 
            min(MinCost[mask], FindCost(sub) + FindCost(mask ^ sub));
  return MinCost[mask];
}

Proqramın əsas hissəsi. Hələ göndərmə xərcləri məlum olmayan uyğun alt çoxluqlar üçün MinCost[mask] = -1 təyin edirik.

memset(MinCost,0xFF,sizeof(MinCost));

Nəticə məlumatlarını oxuyuruq.

scanf("%d %d",&n,&p);
for(i = 0; i < n; i++) scanf("%d",&m[i]);

Əgər müəyyən alt çoxluğun xərcləri -dən çox deyilsə (daha az deyil, çünki p = 1000 ola bilər), onda xüsusi təklifi onlardan istifadə etmək olmaz. Belə alt çoxluqlar üçün dərhal MinCost[mask] əşyaların xərcləri cəmi kimi hesablanır. Əgər əşyaların xərcləri tam olaraq -ə bərabərdirsə, MinCost[mask] = p təyin edirik.

Buna görə, balaca alt çoxluqlara aid məlum olan minimal xərcləri əvvəlcədən doldururuq. Bu, proqramın işini daha səmərəli edir.

for (mask = 0; mask < (1 << n); mask++)
{
  for(Cost = i = 0; i < n; i++)
    if (mask & (1 << i)) Cost += m[i];
  if (Cost == 1000) Cost = p;
  if (Cost <= 1000) MinCost[mask] = Cost;
}

Cavabı çıxarırıq. Bütün əşyalar cəmi 2^n - 1 maskası ilə təyin olunur, onun ikilik təqdimatı ədədindən ibarətdir.

printf("%u\n",FindCost((1 << n) - 1));