Cut Tiles

Problem

Enzo is doing renovation for his new house. The most difficult part is to buy exactly the right number of tiles. He wants N tiles of different sizes. Of course they have to be cut from the tiles he bought. All the required tiles are square. The lengths of side of the tiles are 2S12S2, ..., 2SN. He can only buy a lot of tiles sized M*M, and he decides to only cut tiles parallel to their sides for convenience. How many tiles does he need to buy?

Input

The first line of the input gives the number of test cases: TT lines follow. Each line start with the number N and M, indicating the number of required tiles and the size of the big tiles Enzo can buy. N numbers follow: S1S2, ... SN, showing the sizes of the required tiles.

Output

For each test case, output one line containing "Case #x: y", where x is the test case number (starting from 1) and y is the number of the big tiles Enzo need to buy.

Limits

1 ≤ 2Sk ≤ M ≤ 2^31-1.

Small dataset

1 ≤ T ≤ 100.
1 ≤ N ≤ 20.

Large dataset

1 ≤ T ≤ 1000.
1 ≤ N ≤ 500.

Sample


Input 
 

Output 
 
4
1 6 2
2 6 2 2
3 6 2 1 1
7 277 3 8 2 6 1 3 6

Case #1: 1
Case #2: 2
Case #3: 1
Case #4: 2

抄了大神们的代码,思路是一块瓷砖被裁去一块砖以后剩余的部分可以变成两块新瓷砖。仔细想想,也有两种不同的表示方法,但由于我们需要的瓷砖都是正方形的,因此裁剪剩余的部分的时候应当选择使得两块砖的长宽差距较小的那一种。(自己在纸上画个长方形你就清楚了)

而整个选取过程是个贪心的过程,我们以从大到小的顺序裁剪瓷砖,这样能够保证大的瓷砖不会因为小瓷砖的碎片先裁剪而不能容纳。


#include <iostream>
#include <fstream>
#include <vector>
#include <set>
#include <algorithm>
#include <functional>

using namespace std;

typedef pair<int, int> Tile; // width, height

inline void adjust(Tile &tile) // put the smaller edge in front
{
    if (tile.first > tile.second)
        swap(tile.first, tile.second);
}

void work_single(istream &input, ostream &output)
{
    int N, M, big_tile_count = 0;
    input >> N >> M;
    multiset<Tile> tiles;
    vector<int> S(N);
    for (int i = 0; i < N; i++)
        input >> S[i];
    // put tiles from large to small
    sort(S.begin(), S.end(), greater<int>());
    for (int s : S)
    {
        int width = (1 << s);
        // find a large enough that can contains current tile
        auto ite = tiles.lower_bound(Tile(width, width));
        Tile tile;
        if (ite == tiles.end())
        {
            tile = Tile(M, M);
            ++big_tile_count;
        }
        else // fetch the large enough tile
        {
            tile = *ite;
            tiles.erase(ite);
        }
        // cut the tile into three
        // try to make the tiles not too thin because the tiles are squares
        Tile tile_a(tile.first, tile.second - width);
        Tile tile_b(tile.first - width, width);
        adjust(tile_a);
        adjust(tile_b);
        tiles.insert(tile_a);
        tiles.insert(tile_b);
    }
    output << big_tile_count << endl;
}

void work(istream &input, ostream &output)
{
    int case_count;
    input >> case_count;
    for (int i = 0; i < case_count; i++)
    {
        output << "Case #" << i + 1 << ": ";
        work_single(input, output);
    }
}

int main(int argc, char *argv[])
{
    bool use_stdio = false;
    if (use_stdio)
        work(cin, cout);
    else
    {
        ifstream input("D-large-practice.in");
        ofstream output("D-large-practice.out");
        work(input, output);
    }
    return 0;
}

另外一种适用于更多输入(N大于500)的思想是现将大的瓷砖切成2的幂次倍,统计以2的n次幂为大小的瓷砖的个数。假设目前有k个大瓷砖,那么它可以切分成的小瓷砖数可以计算出来,而需要的2的n次幂大小的瓷砖数目也是有的,那么可以直接判断当前的k块瓷砖能否满足需求。

判断方法如下:在所有的需要的瓷砖中,先找最大的瓷砖(从大到小的顺序),如果有当前大小的瓷砖,那么直接做减法;如果没有当前大小的,那么找比它大的瓷砖,直接切分成当前大小的瓷砖。因为当前需要的瓷砖就是最大的,因此可以放心的切分,又因为比当前需要的瓷砖大的所有瓷砖都是2的幂,因此可以整除。如果比它大的瓷砖切割完了还不够,就要继续找更大的。如果找不到,则返回false,说明不能满足需要。

那么k究竟是多少呢?这里使用[1, N]中的二分查找。为什么这样的方法比较快呢?加入N=10000,使用上面的算法,则需要判断10000次,而使用二分查找,只需要判断100次即可找出答案。因此,这种方法比较适合N比较大的时候。

#include <iostream>
#include <fstream>

using namespace std;

void calc_tiles(unsigned total[32], unsigned tile_size)
{
    for (unsigned i = 0; i < 32; i++)
        total[i] = 0;
    unsigned a = tile_size;
    unsigned bit = 0;
    while (a > 0)
    {
        if (a & 1)
        {
            unsigned n = tile_size / (1 << bit);
            total[bit] = (2 * n - 1);
        }
        a >>= 1;
        ++bit;
    }
}

void copy_tiles(unsigned total[32], unsigned new_total[32], unsigned k)
{
    for (unsigned i = 0; i < 32; i++)
        new_total[i] = total[i] * k;
}

// subtract count tiles of size 2^m
bool subtract(unsigned total[32], unsigned m, unsigned count)
{
    if (total[m] >= count)
    {
        total[m] -= count;
        return true;
    }

    for (unsigned n = m + 1; n < 32; ++n)
    {
        total[m] += total[n] * (1 << ((n - m) << 1));
        total[n] = 0;
        if (total[m] >= count)
        {
            total[m] -= count;
            return true;
        }
    }
    return false;
}

bool can_cover(unsigned total[32], unsigned need[32])
{
    for (int i = 31; i >= 0; --i)
    {
        if (!subtract(total, i, need[i]))
            return false;
    }
    return true;
}

void work_single(istream &input, ostream &output)
{
    int N, M, val;
    input >> N >> M;
    unsigned need[32];
    for (int i = 0; i < 32; i++)
        need[i] = 0;
    for (int i = 0; i < N; i++)
    {
        input >> val;
        ++need[val];
    }

    unsigned total[32], single[32];
    calc_tiles(single, M);
    int i = 1, j = N, ans = -1;
    while (i <= j)
    {
        int mid = (i + j) >> 1;
        copy_tiles(single, total, mid);
        if (can_cover(total, need))
        {
            ans = mid;
            j = mid - 1;
        }
        else
            i = mid + 1;
    }

    output << ans << endl;
}

void work(istream &input, ostream &output)
{
    unsigned case_count;
    input >> case_count;
    for (unsigned i = 0; i < case_count; i++)
    {
        output << "Case #" << i + 1 << ": ";
        work_single(input, output);
    }
}

int main(int argc, char *argv[])
{
    bool use_stdio = false;
    if (use_stdio)
        work(cin, cout);
    else
    {
        ifstream input("D-large-practice.in");
        ofstream output("D-large-practice2.out");
        work(input, output);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值