一、题目
1、题目描述
给你一个长度为 n n n 的整数数组 a [ i ] a[i] a[i] 和一个整数 m m m,满足 2 m ≥ n 2m \ge n 2m≥n。总共有 m m m 个篮子,编号为 1 1 1 到 m m m。你需要把所有 n n n 个整数分到这些篮子中,且每个篮子至多有 2 2 2 个整数。一种分配方案的 与和 定义为每个数与它所在篮子编号的 按位与运算 结果之和。
样例输入:a = [1,2,3,4,5,6], m = 3
样例输出:9
2、基础框架
- C语言 版本给出的基础框架代码如下:
int maximumANDSum(int* a, int n, int m){
}
3、原题链接
二、解题报告
1、思路分析
(
1
)
(1)
(1) 看到题目以后,云里雾里,不知道从哪里下手,哎?突然发现,数据范围竟然这么小,难道是暴力枚举吗?
(
2
)
(2)
(2) 但是仔细一下,哦,原来是状态压缩。
(
3
)
(3)
(3) 每个篮子至多只能放2个整数。换言之,每个篮子可以放的整数的个数是
0
0
0、
1
1
1、
2
2
2;
(
4
)
(4)
(4) 也就是说,如果一个篮子里面已经有
2
2
2 个整数了,那么无论这个篮子里之前是什么整数,都不能再放进来了;如果一个篮子里面有
1
1
1 个整数,那么无论它是什么,都只能放 1 个整数进来。
(
5
)
(5)
(5) 所以说,一个篮子里到底放了几个整数,我不需要关心,我只关心它放了几个整数,并且与和最大。
(
6
)
(6)
(6) 于是,我们想到了 三进制状态压缩。对于每个篮子,是三进制上的一个数位,比如说总共 4 个篮子,第一个篮子有 1 个数,第二个篮子有2个数,第三个篮子没有数,第四个篮子有2个数,我们用一个三进制数来表示如下:
(
1202
)
3
(1202)_3
(1202)3
(
7
)
(7)
(7) 因为总共有
9
9
9 个篮子,所以总的状态数等于
3
9
=
19683
3^9 = 19683
39=19683,状态数很少,所以我们可以枚举所有状态,然后对于当前状态的篮子情况,我们可以选择任意一个篮子进行状态转移。
(
8
)
(8)
(8) 例如, 状态
(
1202
)
3
(1202)_3
(1202)3 ,我们从第 2 个篮子取出一个数,状态会变成
(
1102
)
3
(1102)_3
(1102)3。再从第 4 个篮子取出一个数,状态就变成
(
1101
)
3
(1101)_3
(1101)3,那么一直取,一直爽,最后状态会变成
(
0000
)
3
(0000)_3
(0000)3,这是初始状态。
(
9
)
(9)
(9) 这不就是动态规划吗???
(
10
)
(10)
(10) 直接上记忆化搜索!
2、时间复杂度
O ( n 2 m ) O(n2^m) O(n2m)。
3、代码详解
#define maxs 19684 // (1)
int pow3[11]; // (2)
int cnt[maxs]; // (3)
int dp[18][maxs]; // (4)
#define inf -1 // (5)
int getStateCount(int state) { // (6)
int cnt = 0;
while(state) {
cnt += state % 3;
state /= 3;
}
return cnt;
}
int max(int a, int b) {
return a > b ? a : b;
}
void init() {
int i;
pow3[0] = 1;
for(i = 1; i <= 10; ++i) {
pow3[i] = pow3[i-1] * 3;
}
for(i = 0; i < pow3[9]; ++i) {
cnt[i] = getStateCount(i);
}
}
int getStateBit(int state, int pos) { // (7)
return state / pow3[pos] % 3;
}
int getNextState(int state, int pos) { // (8)
return state - pow3[pos];
}
int dfs(int m, int *a, int cur, int state) { // (9)
int x, i;
int nstate;
if(cur == -1 && state == 0) {
return 0;
}
if(dp[cur][state] != inf) {
return dp[cur][state];
}
dp[cur][state] = -12345;
for(i = 1; i <= m; ++i) {
if( 0 == getStateBit(state, i-1) ) {
continue;
}
nstate = getNextState(state, i-1);
x = dfs(m, a, cur-1, nstate) + (a[cur] & i);
dp[cur][state] = max(dp[cur][state], x);
}
return dp[cur][state];
}
int maximumANDSum(int* a, int n, int m){
int i, j;
int ret = -12345;
init();
memset(dp, inf, sizeof(dp));
for(i = 0; i < n; ++i) {
for(j = 0; j < pow3[m]; ++j) {
if(cnt[j] != (i+1)) { // (10)
continue;
}
dfs(m, a, i, j); // (11)
if(i == n-1) { // (12)
ret = max(ret, dp[i][j]);
}
}
}
return ret;
}
- ( 1 ) (1) (1) 代表状态总数,总共 3 9 3^9 39 个状态;
-
(
2
)
(2)
(2)
pow3[i]
代表 3 i 3^i 3i,预先初始化出来; -
(
3
)
(3)
(3)
cnt[state]
代表 s t a t e state state 这个状态下用了多少个数; -
(
4
)
(4)
(4)
dp[i][state]
代表前面 i i i 个数放到 m m m个篮子中组合出的状态 s t a t e state state 从而得到的最大与和; -
(
5
)
(5)
(5)
inf
代表非法状态; -
(
6
)
(6)
(6)
int getStateCount(int state)
求的是state
这个状态下用了多少个数; -
(
7
)
(7)
(7)
int getStateBit(int state, int pos)
表示求state
这个状态的从低到高第pos
的三进制位对应的值是 0 还是 1 还是 2;
state -> (12201)_3
index 43210
pos -> 3
return -> 2
-
(
8
)
(8)
(8)
int getNextState(int state, int pos)
表示state
这个状态对应的篮子从第pos
位取掉一个数得到的状态;
state -> (12201)_3
index 43210
pos -> 3
return -> (11201)_3
-
(
9
)
(9)
(9)
dfs
实现的就是状态转移,配合记忆化搜索;
d p [ i ] [ s t a t e ] = m a x x = 1 m ( d p [ i − 1 ] [ n s t a t e ] + ( a [ i ] & x ) ) dp[i][state] = max_{x=1}^{m}(dp[i-1][nstate] + (a[i] \& x)) dp[i][state]=maxx=1m(dp[i−1][nstate]+(a[i]&x)) -
(
10
)
(10)
(10)
dp
状态的第二维计算出来的数字个数和第一维数字个数不相等,代表是一个冲突的状态,直接不用计算; - ( 11 ) (11) (11) 计算状态 d p [ i ] [ j ] dp[i][j] dp[i][j];
- ( 12 ) (12) (12) 当 n n n个数用完的时候,取最大与和;
三、本题小知识
一个题目看着复杂的时候,我们先看下它的数据范围,如果数据范围小的话,很可能就是 暴力 或者 状态压缩。
四、加群须知
相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」。
那么这里,我整理了「 几十个基础算法 」 的分类,点击开启:
如果链接被屏蔽,或者有权限问题,可以私聊作者解决。
大致题集一览:
为了让这件事情变得有趣,以及「 照顾初学者 」,目前题目只开放最简单的算法 「 枚举系列 」 (包括:线性枚举、双指针、前缀和、二分枚举、三分枚举),当有 一半成员刷完 「 枚举系列 」 的所有题以后,会开放下个章节,等这套题全部刷完,你还在群里,那么你就会成为「 夜深人静写算法 」专家团 的一员。
不要小看这个专家团,三年之后,你将会是别人 望尘莫及 的存在。如果要加入,可以联系我,考虑到大家都是学生, 没有「 主要经济来源 」,在你成为神的路上,「 不会索取任何 」。
🔥联系作者,或者扫作者主页二维码加群,加入刷题行列吧🔥
🔥让天下没有难学的算法🔥
C语言免费动漫教程,和我一起打卡! 🌞《光天化日学C语言》🌞
让你养成九天持续刷题的习惯 🔥《九日集训》🔥
入门级C语言真题汇总 🧡《C语言入门100例》🧡
组团学习,抱团生长 🌌《算法零基础100讲》🌌
几张动图学会一种数据结构 🌳《画解数据结构》🌳
竞赛选手金典图文教程 💜《夜深人静写算法》💜