Делаем три массива: первый для хранения 100тыс. чисел unsigned int, второй — для хранения 1000 «указателей» на верхушки стеков, третий — для хранения 100тыс. связей («указателей» на следующий элемент) между элементами стеков. (Здесь слово «указатель» я намеренно взял в кавычки!)
Пакость заключается в том, что если использовать указатели (pointer), то размер третьего массива будет ~800Кб (по 8 байт на указатель). Если использовать целочисленные индексы по первому массиву, то в сумме первый и третий массивы дадут 400Кб + 400Кб = 800Кб, что тоже много.
Обойти это можно следующим образом.
Идея
Для хранения номера (индекса) следующего элемента стека необходимо 17 бит (2^17 = 131072 > 100000). Т.е. тип short мал.
Числа, которые необходимо хранить в стеках, не более 10^9 (по условию задачи). Для хранения такого числа достаточно 30 бит. Т.е. имеется 2 неиспользуемых бита.
Из (1) и (2) следует, что для третьего массива можно использовать тип unsigned short, а недостающий бит хранить там же, где и число. Оставшийся 1 бит можно использовать как флаг того, что ячейка занята/свободна.
По памяти получается 400Кб (массив с числами) + 200Кб (массив индексов) + 4Кб (массив индексов верхних элементов стеков) = ~604Кб. Проходим.
Реализация PUSH
Функция, реализующая PUSH, получает номер стека и число, которое нужно поместить в стек (данные).
Ищем свободную ячейку по массиву данных (это, см. выше, первый массив), используя флаг занятой ячейки.
Из массива индексов верхних элементов стеков (второй массив) берётся индекс верхнего элемента для этого стека.
В свободную ячейку массива данных заносится число, в 30 младших разрядах которого хранятся данные, бит 30 = 1 — устанавливаем флаг, что ячейка занята, в бит 31 помещаем старший разряд индекса предыдущего элемента стека, полученный на шаге 3.
В ячейку массива индексов (ячейка с тем же индексом, что и для массива данных) заносятся 16 младших битов индекса предыдущего элемента стека, полученный на шаге 3.
В массив индексов верхних элементов стеков для этого стека заносится индекс добавляемого элемента, полученный на шаге 2.
Реализация POP
Функция, реализующая POP, получает номер стека, из которого извлекаются данные, и возвращает это число в качестве результата.
Из массива индексов верхних элементов стеков берётся индекс верхнего элемента для этого стека.
Из массива данных и массива индексов по индексу, полученному на шаге 2, извлекается информация и формируются «чистые» данные и «чистый» индекс предыдущего элемента стека.
В ячейке массива данных (по индексу из шага 2) сбрасывается флаг занятости ячейки.
В массив индексов верхних элементов стеков для этого стека заносится индекс предыдущего элемента, полученный на шаге 3.
В общем, всё достаточно элементарно для реализации. Единственно, что здесь я обошёл вниманием — это алгоритм поиска свободной ячейки в массиве данных, Поскольку тупой линейный поиск приводит к Time limit exceeded. Потребуется некая оптимизация поиска. Но это уже достаточно просто ;-)
Реализовывать это всё можно как в виде отдельных функций, так и в виде класса (C или C++). Разницы большой нет. Просто в первом случае функции будут пользоваться глобальными переменными/массивами, а во втором — членами класса.
Поскольку тупой линейный поиск приводит к Time limit exceeded. Потребуется некая оптимизация поиска. Но это уже достаточно просто ;-)
Вот код:
#include <cstdio>
#define size 100000
unsigned long values[size]; //значения
unsigned long tops[1000]; //верхушки стеков
unsigned short pointers[size]; //"указатели" на предыдущщий элемент
void PUSH ( short, unsigned long );
unsigned long POP ( short );
inline int g_bit( unsigned long val, int pos )
{
return val &= ( 1 << pos );
}
void s_bit( unsigned long&, int, int );
int main()
{
int n;
register int i;
unsigned long b;
short a;
char str[5];
scanf ( "%d", &n );
for ( i = 0; i < n; i++ )
{
scanf ( "%s%d", str, &a );
switch ( str[1] )
{
case 'U':
{
scanf ( "%u", &b );
PUSH ( a-1, b );
};break;
case 'O':
{
printf ( "%u\n", POP ( a-1 ) );
};break;
default:
{
return 1;
};break;
}
}
return 0;
}
void PUSH ( short stack, unsigned long value )
{
register unsigned long j;
//ищем свободную ячейку
for ( j = 0; j < size; j++ )
{
if ( !g_bit( values[j], 30 ) )
break; //в j - свободная ячейка
}
//на всякий случай :)
values[j] = 0;
//В свободную ячейку массива данных заносится число
values[j] = value;
//устанавливаем флаг, что ячейка занята
s_bit( values[j], 30, 1 );
//в бит 31 помещаем старший разряд индекса предыдущего элемента стека, полученный на шаге 3.
if ( g_bit( tops[stack], 17 ) )
s_bit( values[j], 31, 1 );
/*В ячейку массива индексов
заносятся 16 младших битов индекса предыдущего элемента стека, полученный на шаге 3.*/
pointers[j] = tops[stack];
/*В массив индексов верхних элементов стеков для этого стека заносится
индекс добавляемого элемента, полученный на шаге 2.*/
tops[stack] = j;
return;
}
unsigned long POP ( short stack )
{
register int i;
unsigned long clear_data, clear_index;
clear_data = clear_index = 0;
/*Из массива данных и массива индексов по индексу, полученному на шаге 2,
извлекается информация и формируются "чистые" данные и "чистый" индекс предыдущего элемента стека.*/
for ( i = 0; i < 30; i++ )
clear_data += g_bit( values[tops[stack]], i );
clear_index += pointers[tops[stack]];
clear_index += g_bit( values[tops[stack]], 31 );
//В ячейке массива данных (по индексу из шага 2) сбрасывается флаг занятости ячейки.
s_bit( values[tops[stack]], 30, 0 );
/*В массив индексов верхних элементов стеков для этого стека
заносится индекс предыдущего элемента, полученный на шаге 3.*/
tops[stack] = clear_index;
return clear_data;
}
void s_bit( unsigned long &val, int pos, int status )
{
if ( status )
val |= ( 1 << pos );
else
{
unsigned long temp = 0;
for ( int i = 0; i < sizeof( unsigned long ) * 8; i++ )
{
if ( g_bit( val, i ) && i != pos )
temp |= ( 1 << i );
}
val = temp;
}
return;
}
Вердикт:
1220. Stacks Visual C++ 2010 Time limit exceeded 10 0.531 368 КБ
Всё дело, наверное, в алгоритме поиска. Но как искать, если ни линейный, ни двоичный, ни интерполяционный поиски не подходят?
Череп
Вердикт: тяжелый случай незнания работы с Битовыми операциями.
Сброс двух старших битов в 32-бинтом числе:
val &= 0x3fffffff;
Сброс 30-го бита:
val &= ~0x40000000;
Ну и так далее. Операции бинтового сдвига, конечно, быстрые, но лучше все-таки использовать константы.
void PUSH ( short stack, unsigned long value )
{
register unsigned long j;
for ( j = 0; j < size; j++ )
{
if ( !( values[j] & 0x40000000 ) )
break;
}
values[j] = 0;
values[j] = value;
values[j] |= 0x40000000;
if ( tops[stack] & 0x20000 )
values[j] |= 0x80000000;
pointers[j] = tops[stack];
tops[stack] = j;
return;
}
unsigned long POP ( short stack )
{
register int i;
unsigned long clear_data, clear_index;
clear_data = clear_index = 0;
clear_data = 0x3fffffff & values[tops[stack]];
clear_index += pointers[tops[stack]];
clear_index += ( values[tops[stack]] & 0x80000000 );
values[tops[stack]] &= ~40000000;
tops[stack] = clear_index;
return clear_data;
}
Результат тот-же.
P.S.: комментарии в коде почему-то показываются кракозябрами(
Череп
Ляпа: if ( tops[stack] & 0x20000 ) 0x20000 — это 18-й бит, а тебе нужен 17-й.
Ещё есть пара лишних присваиваний.
Ну и поиск свободной ячейки «в лоб». Я же сразу сказал, что не прокатит.
Череп
По поиску, как вариант: завести еще одну индексную переменную, которая бы отслеживала первую незанятую ячейку. При освобождении ячейки (POP) эту переменную настраивать на эту ячейку. При заполнении ячейки (PUSH) искать следующую незанятую ячейку от текущего положения этого индексного указателя.
Можно сделать две индексных переменных: одна отслеживает оставшееся свободное пространство, а вторая отслеживает освободившиеся ячейки и первыми заполняются именно эти ячейки.
Можно сделать и что-то более «интеллектуальное», но я боюсь, что оно будет относительно медленно работать.
porshe, тут как бы путаница в сколько бит и номер бита, если считать от 0. Для представления 100000 нужно 17 бит, т.е. логично сказать, что старший бит 17-й. Но если считать от 0, то он 16-й. Мог бы и сам посчитать ;-)
Ну, я бы не стал выставлять оценки столь категорично. Твой последний вариант кода практически повторяет мой код, за исключением фрагментов, отвечающих за поиск свободной ячейки. И написал ты его сам. Остальные отличия больше косметические. Этот, первый вариант, я написал на C++ с классами. Второй вариант я переписал на C для того, что бы посмотреть будет ли отличие на Timus'е в результатах. Второй вариант ещё больше похож на твой код. Так что, всё нормально. А если учесть некую объективную разницу в опыте... Я скажу так, что в 8 классе я программы на C++ не писал ))
Чем больше будешь писать программ, тем лучше будет код. И ещё учитывай, что спортивное программирование — это достаточно своеобразная область. Пиши больше, читай книги по алгоритмам, методике программирования — и всё получится. Но практика — это главное.
Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.
А что если разбить так:
То есть тысяча стеков и в каждом стеке по 100 элементов. Это ведь максимально возможное кол-во стеков и значений в них, так?
Как-то так:
Но почему-то:
Нет, не так. В задаче не оговорено максимальное количество элементов в каждом стеке. Т.е. все 100тыс. элементов могут оказаться в одном стеке.
Раз идей больше нет, тогда тогда лови...
Делаем три массива: первый для хранения 100тыс. чисел
unsigned int
, второй — для хранения 1000 «указателей» на верхушки стеков, третий — для хранения 100тыс. связей («указателей» на следующий элемент) между элементами стеков. (Здесь слово «указатель» я намеренно взял в кавычки!)Пакость заключается в том, что если использовать указатели (pointer), то размер третьего массива будет ~800Кб (по 8 байт на указатель). Если использовать целочисленные индексы по первому массиву, то в сумме первый и третий массивы дадут 400Кб + 400Кб = 800Кб, что тоже много.
Обойти это можно следующим образом.
Идея
short
мал.Из (1) и (2) следует, что для третьего массива можно использовать тип
unsigned short
, а недостающий бит хранить там же, где и число. Оставшийся 1 бит можно использовать как флаг того, что ячейка занята/свободна.По памяти получается 400Кб (массив с числами) + 200Кб (массив индексов) + 4Кб (массив индексов верхних элементов стеков) = ~604Кб. Проходим.
Реализация PUSH
Реализация POP
В общем, всё достаточно элементарно для реализации. Единственно, что здесь я обошёл вниманием — это алгоритм поиска свободной ячейки в массиве данных, Поскольку тупой линейный поиск приводит к Time limit exceeded. Потребуется некая оптимизация поиска. Но это уже достаточно просто ;-)
Реализовывать это всё можно как в виде отдельных функций, так и в виде класса (C или C++). Разницы большой нет. Просто в первом случае функции будут пользоваться глобальными переменными/массивами, а во втором — членами класса.
Вот код:
Вердикт:
Всё дело, наверное, в алгоритме поиска. Но как искать, если ни линейный, ни двоичный, ни интерполяционный поиски не подходят?
Вердикт: тяжелый случай незнания работы с Битовыми операциями.
Сброс двух старших битов в 32-бинтом числе:
Сброс 30-го бита:
Ну и так далее. Операции бинтового сдвига, конечно, быстрые, но лучше все-таки использовать константы.
Вот исправленный вариант:
Результат тот-же.
P.S.: комментарии в коде почему-то показываются кракозябрами(
Ляпа:
if ( tops[stack] & 0x20000 )
0x20000 — это 18-й бит, а тебе нужен 17-й.Ещё есть пара лишних присваиваний.
Ну и поиск свободной ячейки «в лоб». Я же сразу сказал, что не прокатит.
По поиску, как вариант: завести еще одну индексную переменную, которая бы отслеживала первую незанятую ячейку. При освобождении ячейки (POP) эту переменную настраивать на эту ячейку. При заполнении ячейки (PUSH) искать следующую незанятую ячейку от текущего положения этого индексного указателя.
Можно сделать две индексных переменных: одна отслеживает оставшееся свободное пространство, а вторая отслеживает освободившиеся ячейки и первыми заполняются именно эти ячейки.
Можно сделать и что-то более «интеллектуальное», но я боюсь, что оно будет относительно медленно работать.
Но...
Как-то так?
porshe, тут как бы путаница в сколько бит и номер бита, если считать от 0. Для представления 100000 нужно 17 бит, т.е. логично сказать, что старший бит 17-й. Но если считать от 0, то он 16-й. Мог бы и сам посчитать ;-)
Мой вариант решения задачи 1220 Stacks
Есть не очень аккуратные фрагменты... однако работает ))
Тот момент, когда понимаешь, что ты и твой код полное г..вно. :(
Спасибо Череп, selevit, было интересно!
Ну, я бы не стал выставлять оценки столь категорично. Твой последний вариант кода практически повторяет мой код, за исключением фрагментов, отвечающих за поиск свободной ячейки. И написал ты его сам. Остальные отличия больше косметические. Этот, первый вариант, я написал на C++ с классами. Второй вариант я переписал на C для того, что бы посмотреть будет ли отличие на Timus'е в результатах. Второй вариант ещё больше похож на твой код. Так что, всё нормально. А если учесть некую объективную разницу в опыте... Я скажу так, что в 8 классе я программы на C++ не писал ))
Чем больше будешь писать программ, тем лучше будет код. И ещё учитывай, что спортивное программирование — это достаточно своеобразная область. Пиши больше, читай книги по алгоритмам, методике программирования — и всё получится. Но практика — это главное.