Тема: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

На квадратному столі розкладено N прямокутних аркушів паперу, сторони у яких паралельні границям стола. Відомо цілі координати пар протилежних вершин кожного прямокутника у системі координат, центр якої лежить в одній з вершин стола, осі  проходять через дві перпендикулярні границі столу.
   Потрібно підрахувати мінімальну кількість цвяхів, необхідних для того, щоб прибити всі аркуші до столу. Аркуш вважається прибитим, якщо в середині нього проходить хоча б один цвях (цвяхи, що проходять через границі аркушу, не прибивають його).


Технічні умови
   Вхідні дані
   В першому рядку число N (1 ≤ N ≤ 20). У наступних N рядках по чотири невід’ємних цілих числа – координати двох протилежних вершин кожного прямокутника, числові значення не перевищують 100.
   Вихідні дані
   Одне число – мінімальна кількість використаних цвяхів.

2 Востаннє редагувалося koala (06.01.2014 12:32:40)

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Тупий варіант: створити масив 100х100 (стіл), кожний аркуш додає +1 до клітинок під ним, і в циклі "вбиваємо цвях" в максимум та віднімаємо "прибиті" аркуші. Можна спростити до 40х40 за бажання.
Особливо інших варіантів і не бачу.
І якщо 4-й день "граєтесь", то викладайте напрацювання.

3

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Я намагався через два масиви її зробити,ввівши Ох в один а Оу в інший,проте нічого з цього не вийшло,так як завелика кількість прямокутників,макс=20,моя ідея така,що необіхдно створити умову перевірки перетину векторі - діагоналей прямокутників,і вже тоді ця кількість цвяхів або зменшується відповідно на кількість перетинів або залишається рівною кількості прямокутників.

4 Востаннє редагувалося koala (06.01.2014 13:01:27)

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

drummerdolbit написав:

Я намагався через два масиви її зробити,ввівши Ох в один а Оу в інший,проте нічого з цього не вийшло,так як завелика кількість прямокутників,макс=20

Тобто два масиви по 40 елементів - це забагато? Які у вас обмеження в часі і пам'яті, 1мс і 1кБ?
Хоча правильно, це потрібно (може, в іншому форматі, але якщо не знаєте іншого - такий теж годиться).

drummerdolbit написав:

необіхдно створити умову перевірки перетину векторі - діагоналей прямокутників,і вже тоді ця кількість цвяхів або зменшується відповідно на кількість перетинів або залишається рівною кількості прямокутників.

Це буде довше і складніше, бо ще є потрійні і більші перетини.

Навіть формувати масив точок не треба - просто перебираємо в подвійному циклі всі точки столу (центри цілочислових квадратів, їх 100х100), знаходимо ту, яка з'єднує максимум аркушів, прибираємо прибиті аркуші, знову перебираємо точки...

5

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Стосовно 80 точок,я мав на увазі,що за моєю ідеєю про вектори,необхідно з’ясувати перетин,перетин відбувається,коли(x1<x<x2) і відповідно для у,а тим що це забагато я хотів сказати,що якщо через розгалуження робити,то це вже занадто складно,а Ваш варіант мене зацікавив,треба детальніше розглянути його,мій вже давно відпав.

6

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Ну тут явно всі точки цілочислові - прямий натяк, що задачу треба дискретно розв'язувати. Нам же все одно, в яку точку перетину цвях вбивати - якщо прямокутники (1,1 - 3,3) і (2,2 - 4,4) перетинаються по (2,2 - 3,3), то можна і в (2.1, 2.1), і в (2.9, 2.9) вбити, це одне й те саме. Простіше казати, що в прямокутнику (2,2 - 3,3) є рівно одна точка, що нас цікавить.

7

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Згоден, але для вхідних даних формувати масив точок все одно необхідно.

8 Востаннє редагувалося koala (06.01.2014 13:16:45)

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Ви, я так розумію, ще записи (record) не вивчали? А користувацькі функції (function, procedure)?

9

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Правильно розумієте.

10

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

koala написав:

Навіть формувати масив точок не треба - просто перебираємо в подвійному циклі всі точки столу (центри цілочислових квадратів, їх 100х100), знаходимо ту, яка з'єднує максимум аркушів, прибираємо прибиті аркуші, знову перебираємо точки...

Цей алгоритм не завжди буде давати оптимальний розв'язок. Крім того, що робити, коли є кілька точок, які "прибивають" однакову кількість аркушів?

P.S. Тему краще перемістити в розділ "Алгоритми та структури даних" (http://replace.org.ua/forum/31/), бо Паскаль тут ні до чого. Головне придумати правильний алгоритм, а запрограмувати його можна буде на будь-якій мові.

11 Востаннє редагувалося koala (06.01.2014 14:39:30)

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Про перемістити - підтримую.
Про неоптимальність - не погоджуюся, мені інтуїція каже, що тут все надто рівно і лінійно, щоб були ситуації, коли прибивання більшої кількості аркушів викликало збільшення кількості цвяхів. Можете контрприклад скласти?
Ну і про однакову кількість - звісно, все одно, де прибивати.

Оновлено: сам знайшов контрприклад: три аркуші перетинаються, кожен з них перетинається із ще одним (всього аркушів 6). Центральний перетин видаляє одночасно 3 аркуші, але 3 крайніх аркуші потребують ще 3 цвяхів. Якщо ті 3 цвяхи вбити, то початковий цвях вже не потрібен.
Так, задачка не така проста... Пішов думати далі.

12

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Про контрприклад. Малювати складно, тому спробую пояснити словами.
Нехай є 4 прямокутники: А, В, С, Д, причому спільні точки перетину мають:
1) А і В,
2) В і С,
3) С і Д.
Відповідно, в кожній із цих точок можна "прибити" по 2 листи. Якщо ми перший цвях заб'ємо в точці 2), то приб'ємо листи В і С, а А і Д залишаться неприбитими. Вони не мають перетину, тому знадобиться ще 2 цвяхи. Тобто всього 3 цвяхи.
Але якщо прибити цвяхи в точках 1) і 3), то достатньо 2 цвяхи.

Подякували: koala1

13

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

yooll написав:

Про контрприклад. Малювати складно, тому спробую пояснити словами.
Нехай є 4 прямокутники: А, В, С, Д, причому спільні точки перетину мають:
1) А і В,
2) В і С,
3) С і Д.
Відповідно, в кожній із цих точок можна "прибити" по 2 листи. Якщо ми перший цвях заб'ємо в точці 2), то приб'ємо листи В і С, а А і Д залишаться неприбитими. Вони не мають перетину, тому знадобиться ще 2 цвяхи. Тобто всього 3 цвяхи.
Але якщо прибити цвяхи в точках 1) і 3), то достатньо 2 цвяхи.

Занадто важко як на мене,для такої легкої задачі,цю задачу колись зробила моя знайома,наскільки я пам’ятаю вона зробила це досить швидко,і на онлайн компіляторі зарахувало на 100%.

Має бути легший спосіб,однозначно.

14

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Пропоную розпочати із структури даних:

type
  TSheetPart = Boolean;
  TCell = array [1 .. 20] of TSheetPart;
  TTable = array [1 .. 100, 1 .. 100] of TCell;

Тоді якщо лист №7 займає крім інших клітинку 3:4 то матимемо:

var
  MyTable: TTable;
//...
  MyTable[3, 4][7] := True;

Ну а розв’язок простим перебором усіх можливих комбінацій. Принаймні для початку.

15

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Усіх можливих комбінацій буде явно забагато.

16

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Варіанти для повного перебору можна дещо зменшити.
1) Знайти ізольовані листи (ті, що не перетинаються з жодним із інших) і прибити кожен із них одним цвяхом. Таким чином зменшиться кількість листів.
2) Як потенційні місця для цвяхів розглядати лише ті, що є перетином двох і більше листів.
3) Знайти нижню оцінку кількості цвяхів. Це можна зробити із методу, який пропонував koala. Потрібно із матриці 100*100 взяти найбільше значення, потім найбільше серед інших і т.д., поки їх сума не досягне N. Кількість цих чисел і буде нижньою оцінкою.

P.S. Метод повного перебору в конкретній постановці (N<=20) застосувати, мабуть, можна, але взагалі то це не метод))

17

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

Зараз я бачу щось таке:
1. Складаємо список місць для цвяхів. Для кожної клітинки визначити множину аркушів, які на ній лежать.
2. Якщо ця множина є підмножиною множини, що вже є в списку, то ігноруємо її, інакше додаємо. Якщо в списку були підмножини доданої множини, видаляємо їх.
3. Шукаємо аркуші, що є членами однієї множини зі списку. Прибиваємо.
4. Ті аркуші, що лишилися, будуть утворювати один чи декілька циклів. Перебираємо варіанти їх розірвання - скажімо, рекурсивною процедурою.
Це все можна і через графи описати, так навіть правильніше. Але мороки для початківця забагато.

Подякували: yooll1

18

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

реально забагато,обласна олімпіада того року)

19

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

За goto не придиратися - ліньки було ваяти додаткові прапорці для вкладених циклів.
Система координат - з лівого верхнього кута.

Код
#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>

using std::cout;
using std::endl;
using std::ios_base;
using std::ifstream;
using std::getline;
using std::vector;

struct      MyRect
{
    int     xTopLeft,
            yTopLeft,
            xBottomRight,
            yBottomRight;
};

vector<MyRect>    pointList;

const int   SIZE                 = 101;

bool    isEmptyRect(MyRect r)
{
    return ( (r.xBottomRight-r.xTopLeft) == SIZE ) && ( (r.yBottomRight-r.yTopLeft) == SIZE );
}

int         N,
            nails;

int         field[SIZE][SIZE];

bool    readData();
void    coverField();

bool    crossAreas(MyRect, MyRect);
int     findMax();
int     nailField();
MyRect  findFirstRect(int);

int main()
{
    N       = 0;
    nails   = 0;

    if( !readData() )
    {
        cout << "Cannot read data from file!" << endl;
        return 1;
    }

    while(!pointList.empty())
    {
        memset(field, 0, sizeof(field));
        coverField();
        nails += nailField();
    }

    cout << "Nails: " << nails << endl;

    return 0;
}

/* Find max-level of covering */
int     findMax()
{
    int     m = field[0][0];

    for(int i = 0; i < SIZE; i++)
        for(int j = 0; j < SIZE; j++)
            m = ( field[i][j] > m )? field[i][j] : m;

    return  m;
}

/* Find the first of the Max-covered squares */
MyRect  findFirstRect(int level)
{
    MyRect  r;
    int     x, y;       // top left

    for(y = 0; y < SIZE; y++)
        for(x = 0; x < SIZE; x++)
        {
            if(field[y][x] == level)  // top left corner
                goto TOP;
        }

TOP:
    int w = SIZE, h = SIZE;

    for(int jX = x; jX < SIZE; jX++)
        if(field[y][jX] != level)
        {
            w = jX;
            break;
        }

    for(int iX = x; iX < w; iX++)
        for(int jY = y; jY < SIZE; jY++)
            if(field[jY][iX] != level)
            {
                h = jY;
                goto BOTTOM;
            }
BOTTOM:
    r.xTopLeft = x;         r.yTopLeft = y;
    r.xBottomRight = w;    r.yBottomRight = h;

    return r;
}

/* Checking whether the Point and Square cross each other */
bool     crossAreas(MyRect p, MyRect s)
{
    return  ( p.xTopLeft <= s.xTopLeft )            &&
            ( p.xBottomRight >= s.xBottomRight)     &&
            ( p.yTopLeft <= s.yTopLeft )            &&
            ( p.yBottomRight >= s.yBottomRight)
            ;
}

/* Find the max-covered square, nail it - and log about it (by clearing pointList) */
int     nailField()
{
    if( !pointList.size() )         // No area to compare with the field
        return 0;

    int     level       = findMax();

    MyRect  area = findFirstRect(level);
    if( isEmptyRect(area) )
        return 0;

    vector<MyRect> newPoints;

    // Leave only not-crossed
    for(size_t i = 0; i < pointList.size(); i++)
        if( !crossAreas(pointList[i], area) )   // NOT crossed
            newPoints.push_back(pointList[i]);

    pointList.clear();
    pointList = newPoints;

    return 1;   // Nail!
}



bool    readData()
{
    ifstream    f;
    f.open("/work/test/schooltask/points.txt", ios_base::in);

    if(! f.is_open())
    {
        cout<<"Fail to open file!" << endl;
        return false;
    }

    char    str[20]     ={0};

    f.getline(str, 19, '\n');        // Read first line - 'N'

    if( !sscanf(str, "%d", &N) || N<1 || N>20 )
    {
        cout << "Invalid value of \'N\'!" << endl;
        f.close();
        return false;
    }

    for(int i = 1; i <= N && !f.eof(); i++)
    {
        memset(str, 0, 20);
        f.getline(str, 19, '\n');
        MyRect  r;
        if( sscanf(str, "%d;%d;%d;%d", &r.xTopLeft, &r.yTopLeft, &r.xBottomRight, &r.yBottomRight) != 4)
        {
            cout << "Row #" << i << " data is invalid." << endl;
            continue;
        }
        pointList.push_back(r);
    }

    cout << "Data has been loaded." << endl;

    f.close();
    return true;
}

void coverField()
{
    for( vector<MyRect>::iterator iter = pointList.begin(); iter != pointList.end(); iter++ )
    {
        for(int iX = (*iter).xTopLeft; iX < (*iter).xBottomRight; iX++)
            for(int jY = (*iter).yTopLeft; jY < (*iter).yBottomRight; jY++)
                field[jY][iX] ++;
    }
}

Суть коду у двох словах:
1. Шукаємо зону найбільшого покриття (площа, вкрита найбільшою кількістю аркушів). Якщо зон кілька - беремо першу-ліпшу.
2. Видаляємо зі списку вхідних аркушів ті, які можуть бути прибиті у цій покритій зоні.
3. Збільшуємо показник кількості цвяхів на одиничку.
4. Повторюємо операції (1)-(3), поки аркуші не закінчаться.


Тестові дані

Файл points.txt

7
5;6;15;20
30;6;50;15
10;15;25;25
70;70;80;90
40;50;45;55
36;10;45;22
70;0;80;10

Два перетини по два аркуші плюс три окремих аркуші. Результат - 5 цвяхів.

20

Re: Дуже потрібна допомога по задачі,вже четвертий день граюсь з нею

А такі дані:

6
1;2;4;3
3;2;4;5
3;4;4;7
1;6;4;7
3;4;6;5
5;4;8;5