ACM - Псков

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » ACM - Псков » Timus » Задача 1658


Задача 1658

Сообщений 1 страница 18 из 18

1

Условие:http://acm.timus.ru/problem.aspx?space=1&num=1658
Очень интересная задачка ))
Я написал какую-то дикую динамику с очередями. (BFS с меморизацией)

Интересный момент - в этой задаче очень хорошо проявляется разница
между STL очередью и самописной.

STL очередь:              Accepted,   0.203 с,    7 453 КБ
Самописная очередь:  Accepted,   0.125 c,   13 597 КБ  11 строчка рейтинга "лучшие попытки"   :cool:

Код:

Код:
#include <iostream>
#include <queue>
using namespace std;

char sq[10] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81};
char l[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
char d[901][8101];

queue <short int> q1;
queue <short int> q2;
int qps=0;


char vec[10000];
int vp=0;

void ans(int &a, int &b){  // Сбор ответа по родителям
     vp=0;
     while(d[a][b]){
          vec[vp]=d[a][b];
          vp++;
          qps=d[a][b]-48;
          a-=qps;
          b-=sq[qps];
     }
     if(vp>100){
          printf("No solution\n");
     }else {
          while(vp){
               vp--;
               printf("%c",vec[vp]);
          }
          printf("\n");
     }
}

int main(){
     short int a,b,o,p;
     q1.push(0);
     q2.push(0);
     while(!q1.empty()){  //Собственно BFS, рассчитывает ответы сразу для всех возможных случаев
          a=q1.front();
          b=q2.front();
          q1.pop();
          q2.pop();
          for(int i=1;i<10;i++){   //По идее, в этом месте i++ делается ~15 000 000 раз. Замена на ++i замедляет (!!!!!!)  на 0.02 с.
               o=a+i;
               p=b+sq[i];
               if(o<901 && p<8101 && !d[o][p]){
                    d[o][p]=l[i];
                    q1.push(o);
                    q2.push(p);
               }
          }
     }
     int t;
     int e,f;
     scanf("%d",&t);
     for(int test=0;test<t;test++){a
          scanf("%d%d",&e, &f);
          if(e == 0 && f == 0){
               printf("0\n");
          }else if(e == 0 || f ==0){
               printf("No solution\n");
          }else if(e > 900 || f > 8100 || !d[e][f]){
               printf("No solution\n");
          }else ans(e,f);
     }
     return 0;
}

2

А можно было вместо всяких очередей сделать просто очередь на массиве (как обычно и делают), и получить сразу 0.078 мс с 35 мб ;)

Код:
pair<short,short> q[901*8101];
char dst[901][8101],  ans[901];

int main() {
   memset (dst, -1, sizeof dst);
   int qh=0, qt=0;
   q[qt++] = make_pair(0,0);
   dst[0][0] = 0;
   while (qh<qt) {
      short sum = q[qh].first,  sum2 = q[qh++].second;
      if (dst[sum][sum2] < 100)
         for (int i=1; i<=9; ++i)
            if (sum+i<=900 && sum2+i*i<=8100 && dst[sum+i][sum2+i*i] == -1) {
               dst[sum+i][sum2+i*i] = dst[sum][sum2] + 1;
               q[qt++] = make_pair (sum+i, sum2+i*i);
            }
   }

   int ts;
   cin >> ts;
   for (int j=0; j<ts; ++j) {
      int sum, sum2;
      scanf ("%d%d", &sum, &sum2);
      if (sum > 900 || sum2 > 8100 || dst[sum][sum2] == -1) {
         puts ("No solution");
         continue;
      }
      char * ptr = ans;
      for (int i=1; i<=9; ++i)
         while (sum-i>=0 && sum2-i*i>=0 && dst[sum-i][sum2-i*i]+1 == dst[sum][sum2]) {
            *ptr++ = char(i+'0');
            sum -= i,  sum2 -= i*i;
         }
      *ptr = 0;
      puts (ans);
   }

}

А если уж не хочется памяти много хавать, то зацикливаем очередь (из каких-то соображений её хватит небольшой) и получаем 0.062 мс и 7.7 мб :)

P.S. Задача халявная, но я когда её на контесте решал, такую тупую багу посадил... В конце не проверял, что ответ действительно <=100 :) Минут 40 искал ошибку :D

3

Можно разъяснить почему это работает быстрее? make_pair быстрее, чем 2 прямых присвоения? Перемножить 2 инта быстрее, чем взять результат из массива?

Переписал решение - массив [901][8101] свернул в одномерный. Соответственно осталась одна интовая очередь.
0.078 с 13 585 КБ

4

В смысле? Если сравнивать с тем, когда очередь не на массиве, то ясно, откуда большая разница в скорости.
Всякая мелочь типа make_pair или i*i в данном случае не имеет сильного влияния (поэтому я бы не стал захламлять код оптимизацией таких вещей). Имхо основное время тратится на беготню по памяти при операциях с dst[][].
Ну а разница 78-62 вообще в пределах погрешности. Вот 46 мс у лидера - уже значительный отрыв.

Пары вообще, как мне кажется, имеют максимальную производительность. Хотя филигранными замерами времени никогда не занимался :)

Отредактировано e-maxx (2009-01-19 20:00:06)

5

Тут ещё момент - в моём первоначальном решении я выводил ответ посимвольно используя printf
Замена на puts ускоряет на с 0,125 до 0,093. Ибо если посчитать, то объём вывода - до 10 мб.

И на счёт захламления оптимизациями. Сейчас заполнение массива у меня выглядит вот так:

Код:
while(qps!=qpf){
     a=q[qps];
     ++qps;
     for(int i=1;i<10;++i){
           b=a+sq[i];
           if(b<7299002 && !d[b]){
                d[b]=l[i];
                q[qpf]=b;
                ++qpf;
           }
     }
}

Где захламление?

6

Пара работает не быстрее, а просто информация лежит в соседних ячейках, а обращение к последовательным ячейкам памяти намного быстрее, чем в два разных места, думаю, если по колдавать так над алгоритмом, чтобы последовательные вычисления ещё как можно дольше находились в КЭШ, то можно и получить время ещё лучше

Ещё вот это:

while(qps!=qpf){
     a=q[qps];
     ++qps;
     for(int i=1;i<10;++i){
           b=a+sq[i];
           if(b<7299002 && !d[b]){
                d[b]=l[i];
                q[qpf]=b;
                ++qpf;
           }
     }
}

Можно, для компактности, так:

while(qps!=qpf){
     a=q[qps++];
     for(int i=1;i<10;++i)
           if((b=a+sq[i])<7299002 && !d[b]){
                d[b]=l[i];
                q[qpf++]=b;
           }
}

А для скорости, вместо цикла for(int i=1;i<10;++i) переписать внутренность 9 раз для разных i
Например так:
while(qps!=qpf){
     a=q[qps++];
     if((b=a+1)<7299002 && !d[b]){d[b]=l[1];q[qpf++]=b;}
     if((b=a+4)<7299002 && !d[b]){d[b]=l[2];q[qpf++]=b;}
     if((b=a+9)<7299002 && !d[b]){d[b]=l[3];q[qpf++]=b;}
     if((b=a+16)<7299002 && !d[b]){d[b]=l[4];q[qpf++]=b;}
     if((b=a+25)<7299002 && !d[b]){d[b]=l[5];q[qpf++]=b;}
     if((b=a+36)<7299002 && !d[b]){d[b]=l[6];q[qpf++]=b;}
     if((b=a+49)<7299002 && !d[b]){d[b]=l[7];q[qpf++]=b;}
     if((b=a+64)<7299002 && !d[b]){d[b]=l[8];q[qpf++]=b;}
     if((b=a+81)<7299002 && !d[b]){d[b]=l[9];q[qpf++]=b;}
}

И так как a - const, можно ещё так:
while(qps!=qpf){
     b=q[qps++];
     if((b+=1)<7299002 && !d[b]){d[b]='1';q[qpf++]=b;}
     if((b+=3)<7299002 && !d[b]){d[b]='2';q[qpf++]=b;}
     if((b+=5)<7299002 && !d[b]){d[b]='3';q[qpf++]=b;}
     if((b+=7)<7299002 && !d[b]){d[b]='4';q[qpf++]=b;}
     if((b+=9)<7299002 && !d[b]){d[b]='5';q[qpf++]=b;}
     if((b+=11)<7299002 && !d[b]){d[b]='6';q[qpf++]=b;}
     if((b+=13)<7299002 && !d[b]){d[b]='7';q[qpf++]=b;}
     if((b+=15)<7299002 && !d[b]){d[b]='8';q[qpf++]=b;}
     if((b+=17)<7299002 && !d[b]){d[b]='9';q[qpf++]=b;}
}

Отредактировано Gubarev Valentin (2009-01-20 03:13:08)

7

Я в суть алгоритма не вникал, а что такое за d[] и когда оно бывает равно 0, а когда нет, и что такое 7299002, откуда оно, а то гляди ещё ускорим, будешь первым!!!

8

Ktulhu
Я про это:

Код:
char sq[10] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81};
char l[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

Конечно, у каждого свои представления, но по мне это некрасиво.

Gubarev Valentin
Насчёт кэша - неправда. Процессору никто не запрещает держать в кэше текущие области сразу из двух массивов.
А вот разворачивание цикла может дать результат.

9

На практике видел, как один и тотже алгоритм на timus в 10 раз быстрее работал, только из-за замены на последовательный доступ к памяти

10

Я и не спорю. При рандомном доступе эффективность кэша равна нулю, а разница в скорости чтения/записи кэша и оперативной памяти - десятки раз.
А вот замена пары на два массива, и наоборот, ничего не меняет - мы либо в одном массиве читаем последовательно, либо в двух, но тоже последовательно.

11

Ты лучше скажи улучшилось ли время и как?
Стоит наверное на указатели перейти?
И повторно: что такое за d[] и когда оно бывает равно 0, а когда нет, и что такое 7299002, откуда оно, а то гляди ещё ускорим, будешь первым

Отредактировано Gubarev Valentin (2009-01-21 01:52:21)

12

Gubarev Valentin написал(а):

что такое 7299002

Это максимальная длина очереди, которая сможет получится в данной задачи (901*8101)...

13

А зачем мы проверяем b<7299002?
На верное не стоит это проверять каждый раз!!!

14

пфф...  шаманство всё =))
Пойми как это работает, а потом "ускоряй". =))
Я знаю что будет работать быстрее...  но надо заново переписать. =)

Код:
#include <iostream>
using namespace std;

int sq[10] = {0, 8102, 16206, 24312, 32420, 40530, 48642, 56756, 64872, 72990};
char l[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
char d[7299002];
char ans [901];
char vec [901];

char * q[1620000];
char ** qps = q;
char ** qpf= q + 1;
char * pt = d;
char * a;
char * b;
char * ogr = d + 7299002;

char * ptr = vec;
char * ptr2 = vec;
char * vtr = ans;
     
int main(){
     int h=1;
     q[0]= d;
     while(qps!=qpf){
          a=*qps++;
          h = 1;
          b = a + sq[h];
          while(h < 10 && b < ogr){
               if(!*b){
                    *b = l[h];
                    *qpf++ = b;
               }
               ++h;
               b = a + sq[h];
          }
     }
     int t;
     int e,f,p;
     scanf("%d",&t);
     for(int test=0;test<t;test++){
          scanf("%d%d",&e, &f);
          p=e*8101+f;
          if(e == 0 && f == 0){
               puts("0");
          }else if(e > 900 || f > 8100 || !d[e*8101+f]){
               puts("No solution");
          }else {
               ptr = vec;
               ptr2 = vec;
               vtr = ans;
               for(int i=1; i<10; ++i)
               while(d[p]){
                    *ptr++ = char(d[p]);
                    p-=sq[d[p]-48];
               }
               if((ptr - vec )>100){
                    puts("No solution");
               }else {
                    while(ptr > ptr2){
                         *vtr++ = char(*--ptr);
                    }
                    *ptr=0;
                    *vtr=0;
                    puts(ans);
               }
          }
     }
     return 0;
}

15

Мне не зачем понимать как это работает, я ускорял существующий алгоритм, а не переделывал его!
И всё таки, если нужно максимум скорости выжать, то это
char l[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
выкинуть, вообще это в любом случае выкинуть,
и развернуть цикл на подобие, как указывалось выше!

Не забудь новый результат по скорости привести, стань первым :)

Отредактировано Gubarev Valentin (2009-01-22 00:43:41)

16

http://acm.timus.ru/rating.aspx?space=1&num=1658

0.031с  9585 КБ

Добавил отсечения. Очередь укоротилась в 3 раза.

Отредактировано Ktulhu (2009-01-22 02:20:14)

17

if(e == 0 && f == 0){
               puts("0");

e и f по условию не могут быть равны 0

Ещё помоему, что бы было решение необходимо выполнение следующих условий (не знаю какие ты отсечения добавил, но может ещё поможет ускорить):
1.  e<=f<=9*e     (s1<=s2<=9*s1)
2.  e+f Должно быть чётным

Отредактировано Gubarev Valentin (2009-01-22 13:22:18)

18

А ещё надо i на j заменить, быстрее будет :D


Вы здесь » ACM - Псков » Timus » Задача 1658