E.Dividing Chocolate
题目:
有 H×W 大小的方格矩阵,每个方格为 0 or 1, 现在可以进行横切与竖切,每次切都是一切到底的,询问最少切多少次可以保证最后的分块中每个分块都有不超过 K 个 1。
数据范围:
1 ≤ H ≤ 10
1 ≤ W ≤ 1000
1 ≤ K ≤ H × W
思路:
观察到题目给的H很小,用二进制暴力枚举横切的情况,二进制数范围为 0 ~ - 1。因为方块的宽度为h,说明可以横切的地方有n-1个位置,需要二进制位位数是n-1,所以二进制数最多枚举到
- 1。如:n=3,n-1=2,因为
=4=0100;而0~
-1:00,01,10,11。
枚举横切时贪心求竖切的次数:
判断是否需要竖切,或者说在哪竖切时,对于横切后的各区域通过枚举列累加计数来判断是否某需要竖切,如果枚举时发现某区域到某列 j 时累计的 1 的个数超过 k ,说明需要在这一列 j 巧克力的左边界竖切一刀,也就是说是将这一列 j 划到下一块;
竖切过一刀后,以刀痕为界,再对右边的方块继续同上按区域枚举列,进而统计每区域 1 的个数,判断是否需要继续竖切,每竖切过一刀就有一个判断再竖切都没有用的情况:即单独判断需要开刀的拿一列(第 j 列) 1 的个数是否大于 k ,如果这一列拥有的 1 的个数已经大于 k ,那么之后再竖着切也没用了,必须横着切,直接跳出枚举竖切的循环,继续枚举横切即可。
反之,可以继续枚举竖切,同上继续枚举竖切直到竖切枚举完时才说明该方案成立;以此类推,用cnt 记录每次方案的刀数,取 Min 。
Code:
//#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
//#define int long long
#define x first
#define y second
#define IOS ios::sync_with_stdio(false);cin.tie(0);
//typedef long long LL;
typedef pair<int, int>PII;
const int N = 200010, INF = 0x3f3f3f3f;
int n, m, k, z;
char s[15][1010];
int c[15];
用来取一个二进制最低位的一与后边的0组成的数
//int lowbit(int x)
//{
// return x & -x;
//}
//
累计二进制数x中1的个数
//int count1(int x)
//{
// int res = 0;
// while (x)
// x -= lowbit(x), res++;
// return res;
//}
//判断是否需要在方块的第j列的右边界竖着切一刀
bool infer(int j)
{
int id = 0; //id代表横切分成的区域的编号
for (int i = 0; i < n; i++) //累计第j列拥有的1的个数
{
c[id] += (s[i][j] == '1');
if (c[id] > k)return true; //如果区域id块的1的个数大于k,需要竖切
if (z >> i & 1)id++; //根据枚举到的横切状态z通过右移&1,判断横切将方块分成几个区域(如:n=3,z=0011时,说明方块被完全横切两道,将方块上下分成了3个区域,依次对应idx=0,1,2)
}
return false;
}
void solve()
{
cin >> n >> m >> k;
for (int i = 0; i < n; i++)cin >> s[i]; //输入巧克力状态
int ans = INF;
for (z = 0; z < 1 << n - 1; z++) //二进制枚举横切的状态0~2^(n-1)
{
memset(c, 0, sizeof(c));
bool ok = false;
int cnt = __builtin_popcount(z); //记录横切了多少刀
//int cnt = count1(z);
for (int j = 0; j < m; j++) //贪心判断竖切多少刀
{
if (infer(j)) //判断此时横切后的状态是否需要竖切
{
cnt++; //竖切一刀
memset(c, 0, sizeof(c)); //切完后,该刀痕右侧的部分竖着的每一块计数从0开始算
ok = infer(j); //判断切后的划分到下一块的的这一列j拥有的1的个数是否大于k
if (ok)break; //如果这一列已经大于k,说明横切少了,再竖切也无用,直接跳出循环继续枚举横切
}
}
if (!ok)ans = min(ans, cnt); //满足条件的方案记录下,取刀数最小值
}
cout << ans << endl;
}
signed main()
{
IOS;
int t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:不得不说,这题我补的过程是真曲折啊,本来看的题解是不带break那条语句的,之后看懂后感觉应该加上应该没错,然后提交试试……一直过不了,一直以为我加的break不对,想半天,最后发现原来是因为我写循环时又将z当局部变量定义了一次,得,局部变量当全局变量用了,,,害-.-`。 本题注意点:
1.二进制枚举也接触过,不过感觉这题确定枚举范围那块还是有些不熟练。
2.还有这个统计二级制中1的个数函数 __builtin_popcount也是第一次见(⊙o⊙)…而且我的VS上编译显示找不到标识符(⊙o⊙)…但是能过。不过也可以用lowbit来实现该函数。
3.该题贪心判断是否竖切的过程也很巧妙,它在横切的基础上分区域记录,而且还在每竖切一次后单独判断该列是否可以说明竖切没用,少横切。