Блог пользователя Jokser

Автор Jokser, 15 лет назад, По-русски

Задача из второго тура SNWS-10. Про медиану. Дано N (1<=N<=10^6) чисел. Числа добавляются в изначально пустой набор в заданном порядке. Требуется определять медиану после каждого добавления числа. 

Я реализовал бинарное дерево поиска по Кормену. Однако получил TL на 11 тесте. Вот код:

struct binary_elem {
long key,size,left,right;
};

vector<binary_elem> BT;

binary_elem BT_init(long key) {
binary_elem res;
res.key=key;
res.size=1;
res.left=-1;
res.right=-1;
return res;
}

void BT_insert (binary_elem z) {
long y=-1,x=0;
BT.push_back(z);
long cur=BT.size()-1;
if (cur==0) return;
while (x!=-1) {
y=x;
BT[x].size++;
if (z.key<BT[x].key) x=BT[x].left; else x=BT[x].right;
}
if (y!=-1)
if (z.key<BT[y].key) BT[y].left=cur; else BT[y].right=cur;
}

long BT_select (long x, long i) {
long r;
while (true) {
if (BT[x].left==-1) r=1; else r=BT[BT[x].left].size+1;
if (i==r) return BT[x].key;
else if (i<r) { x=BT[x].left; } else {x=BT[x].right; i=i-r; }
}
}

int main(){
long n,num,i;
freopen("median.in","rt",stdin);
freopen("median.out","wt",stdout);
scanf("%d",&n);
for (i=0;i<n;i++) {
scanf("%d",&num);
BT_insert(BT_init(num));
long res = BT_select(0,(i+2)/2);
printf("%d ",res);
}
}

Подскажите плз, что не так. Или как ее можно решить по-другому. Я посмотрел, некоторые ее решили за 5-7 минут!


  • Проголосовать: нравится
  • 0
  • Проголосовать: не нравится

15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
мне кажется, что это бинарное дерево не сбалансировано.
а я писал дерево отрезков, со сжатием координат и поиском k-той порядковой статистики за log(n). в первой попытке было TLE, потому что искал за log2 (n)
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    я писал дерево фенвика и тоже искал ответ за log2 (n) . Accept с первой попытки.
    • 15 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      в этом дереве можно искать ответ за log (n) если выбрать размером дерева степень двойки
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    А я сдал в дорешивании с помощью set'а, никаких проблем.
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Все-таки придется эти загадочные деревья отрезков, Фенвика разобрать и понять. Хотя врубиться в эти читерские &, | никак не могу.

Про балансировку ничего конкретного в Кормене не сказано, а уж тем более если его надо балансировать на ходу.

Спс за комменты, буду пробовать дальше.

  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Плохо читали Кормена. Там подробно описаны красно-черные деревья. Хотя для использования на олимпиадах я бы рекомендовал АВЛ- или рандомизированные деревья, т.к. их легче запомнить.
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    & и | - это не читерские операции, а самые базовые понятия программирования. Вообщем то брать в руки Кормана нету смысла пока Вы с ними не разберётесь
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Решал с помощью двух multiset(точнее дорешивал, на олимпе не решил).
Идея такая - один сортит по возрастанию(пусть это будет s2), другой по убыванию(соответсвенно, s1).  Медианный элемент будет в вершине s2. Нам только надо при добавлении нового элемента это поддерживать - т.е. смотреть, в какой сет добавить и, если надо, перекинуть с верхушки одного сета в другой.
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    я примерно также на контесте сдал, для упрощения понимания нарисуй песочные часы :)
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Если я правильно понял, то у меня было аналогичное решение, только вместо двух мультисетов - две priority_queue.
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Я брал итератор в мультисете и каждый раз перемещал его влево/вправо.
Вроде вот это решение прошло:
   freopen("median.in" ,"rt", stdin); freopen("median.out", "wt", stdout);
   
   int i, n;
   cin >> n;
   multiset<int>::iterator p;
   for (i = 1; i <= n; i++) {
      int x;
      scanf("%d", &x);
      a.insert(x);
      if (i == 1) {
         p = a.begin();
      } else if (x < *p) {
         if ((i-1)%2 == 1) --p;
      } else {
         if ((i-1)%2 == 0) ++p;
      }
      printf("%d ", *p);
   }

   fclose(stdin); fclose(stdout);
   return 0;   

Забавно, но эту задачу мы давали своим школьникам на полуфинале ВКОШП http://imcs.dvgu.ru/cats/main.pl?f=problem_text;cpid=710031;sid=;cid=706500
Ее вполне можно решить двумя кучами, если в языке слабая стандартная библиотека (впрочем, в современном мире это натяжка;) ).
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Посмотрела что такое дерево Фенвика, которое на самом деле оказалось деревом отрезков, только проще по реализации.
Описанные далее алгоритмы "песочные часы" действуют примерно так же как это дерево.
И последнее, я вот задачку саму не видела, не решала, к сожалению, объясните пожалуйста почему там не проходит бинарное дерево? заранее благодарю.
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    бинарное дерево проходит, только надо чтобы оно было сбалансировано. Иначе высота дерева может быть намного больше чем log(n). а именно за счет этой сбалансированности и достигается логарифмическое время для всех операций. и общее время O(N * log(N))
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Я не понял, высказывали ли здесь уже эту мысль, но когда нам дали эту (?) задачу на московских сборах перед ВКОШПом, то Миша Пядёркин предложил решение без мощных структур данных.
Идея: будем решать эту задачу с конца. Даны 10^6 посортированных чисел, далее их удаляют в определённом порядке. После каждого удаления необходимо говорить медиану.
Ясно, что можно сделать двухсвязный список (который можно реализовать на массиве), чтобы хранить левое и правое живые числа. На каждом шаге бинпоиском (или за константу -- я пока не понял) находим в списке число, удаляем его и меняем пометки, смещаем при необходимости указатель на медиану (по ссылкам) и говорим её.
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Сумматор + Дихотомия рулят! :)
#include<algorithm>
#include<iostream>
using namespace std;

#define forr(i,x,y) for(int i=(int)(x); i<=(int)(y); i++)

int n,q[1000010],s[1000010],c[1000010],a[1000010],id[1000010];

bool cmp(int i,int j) { return bool(a[i]<a[j]); }

int sum(int i) { int ss=0;
 while(i) { ss+=s[i]; i=i & (i-1); }
 return ss;
}

int main() {
 freopen("median.in","r",stdin);  freopen("median.out","w",stdout);
 scanf("%d",&n);
 forr(i,0,n-1) { scanf("%d",&a[i]); id[i]=i; s[i+1]=0; c[i+1]=0; }
 sort(id,id+n,cmp);
 c[id[0]]=1; q[1]=a[id[0]];
 forr(i,1,n-1) { c[id[i]]=c[id[i-1]]+(a[id[i]]!=a[id[i-1]]); q[c[id[i]]]=a[id[i]]; }
 for(int i=0; i<=n-1; i++) {
  int j=c[i],k=(i+2)/2,l=1,r=n;
  while(j<=n) { ++s[j]; j=(j | (j-1))+1; }
  while(r-l>1) if(sum((l+r)/2)>=k) r=(l+r)/2; else l=(l+r)/2;
  if(sum(l)==k) printf("%d ",q[l]); else printf("%d ",q[r]);
 }
}

Accept + (на SNWS-10 Day 2 Task #1).