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 2S1, 2S2, ..., 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: T. T 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: S1, S2, ... 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
抄了大神们的代码,思路是一块瓷砖被裁去一块砖以后剩余的部分可以变成两块新瓷砖。仔细想想,也有两种不同的表示方法,但由于我们需要的瓷砖都是正方形的,因此裁剪剩余的部分的时候应当选择使得两块砖的长宽差距较小的那一种。(自己在纸上画个长方形你就清楚了)而整个选取过程是个贪心的过程,我们以从大到小的顺序裁剪瓷砖,这样能够保证大的瓷砖不会因为小瓷砖的碎片先裁剪而不能容纳。
#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;
}

690

被折叠的 条评论
为什么被折叠?



