1. 程式人生 > >poj 1020 分蛋糕問題

poj 1020 分蛋糕問題

/*
 *  poj 1020 切蛋糕

    題目大意:
        求能否將一堆小正方形無重疊的拼接成一個大正方形?

    解題思路:
        回溯 + 剪枝

        按照從左到右,從上到下的順序,列舉所有可以放置正方形的單元,嘗試放入合適的小正方形。
        直到無法放入小正方形或者完全放完為止。

        1、因為小正方形的邊長介於1~10之間,所以可以採用計數的方式來儲存小正方形的個數。
        2、採用每列佔用數的方式來儲存大正方形的佔用情況,同時用來查詢下一個可能的安置單元。
        3、每次選擇佔用最少的列作為安置點 -- 該點必然是某個正方形的左上點,因為它的左上沒有其他空置點了!
        4、從大到小選擇小正方形,如果最終所有小正方形可以拼成大正方形的話,所有的小正方形都會用到的,
           同等的情況下選擇大的,等於把更靈活、容易安排的小的放在了後面,有點貪心的感覺。但是如果
           選擇大的不成功,不等於整個拼接就失敗了,應該選擇更小的正方形進行嘗試。也就是傳說中的回溯。
        
    剪枝:
        1、在安置點(最少佔用列)嘗試放置剩餘的最大正方形時,出現超出邊界的情況,可以直接返回false
           因為剩餘空間最大的列居然都無法滿足某個剩餘正方形,則該正方形必然無法放下,因此無需進行
           進一步的搜素

        2、在安置點嘗試放置某邊長為a的小正方形失敗,則所有後續為a的小正方形也無需嘗試,直接進行
           更小邊長正方形的嘗試!

    最後:
        貪心的反例:
            10 8 4 4 3 3 3 3 3 3 3 1 1 1 1 1 -> 邊長為10的大正方形,與2個4x4、7個3x3、5個1x1的小正方形

        如果貪心,會有如下解:
        
            4x4 4x4 空  空
            3x3 3x3 3x3 空
            3x3 3x3 3x3 空

            最終剩餘一個3x3的小正方形無處安放。

        其實該例有解:
                3x3   3x3
            4x4 111   3x3
            3x3 3x3 1 
            3x3 3x3 1 4x4
*/

#include <iostream>
#include <cstring>

namespace {
    using namespace std;

    const int S_MAX = 40;
    int c[11], d[S_MAX]; // c儲存邊長為i的小正方形的個數,d儲存每例的佔用情況

    int n, S;
    bool splitable(int deepth)
    {
        // 所有正方形均被成功安置,返回成功
        if (deepth == n) return true;

        // 尋找安置點,從左到右,從上到下
        int x=S, y;
        for (int j=0; j<S; ++j)
        {
            if (d[j] < x)
            {
                x = d[j];
                y = j;
            }
        }

        // 從大到小嚐試安置小正方形
        for (int i=10; i>0; --i)
        {
            if(c[i]==0) continue;

            if(x+i > S) return false; // 剪枝1

            if(y+i > S) continue; // 該處不能剪,y到頭表示應該換更小的正方形來嘗試,因為最終每個單元都要被用掉

            // 檢視是否足夠單元
            bool bEnough = true;
            for (int k=1; k<i; k++)
            {
                if (d[y+k] > d[y])
                {
                    bEnough = false;
                    break;
                }
            }

            if (!bEnough) continue;

            // 深搜
            c[i]--;
            for (int k=0; k<i; k++) d[y+k] += i;

            if (splitable(deepth+1))
            {
                return true; // 某次成功即返回
            }

            // 回溯
            c[i]++;
            for (int k=0; k<i; k++) d[y+k] -= i;
        }

        return false; // 所有的正方形均嘗試過,失敗
    }
}

int main()
{
    int t;
    cin >> t;

    int sum, si;
    for (int k=0; k<t; k++)
    {
        cin >> S >> n;

        sum = 0; // 記得每次清0,否則只有第一次準
        memset(c, 0, sizeof(c));
        for (int i=1; i<=n; i++)
        {
            cin >> si;
            c[si]++;
            sum += si * si;
        }

        memset(d, 0, sizeof(d));
        if (sum==S*S && splitable(0))
        {
            cout << "KHOOOOB!" << endl;
        }
        else
        {
            cout << "HUTUTU!" << endl;
        }
    }
    
    return 0;
}