【递归/dp】Acwing1243.糖果(IDA*+步调函数/状态压缩+01背包)
题目
题解
动态规划
用二进制表示每种味道尝了没有,如:一共有五种味道,吃了前三种——00111,吃了五种——11111
每包糖果都可以选或不选
dp(i,j) i代表前i包糖果, j代表所能到达的状态(吃了几种种味)
其实就是01背包问题
dp[i-1][j & (~w[i])]+1 意味着:选这包糖果,从前面未选这包的状态+1包
同时不用担心重复问题
前面:1 2 3
后面:3 4 5
当前:j & (~w[i]) 会把 3, 4,5位上的1化为0,可前面应该是1,2,3位都为1才有的选,但通过这种运算,前面:00001也有的选,00011也有的选,00111也有的选
未状压(数组需要开到100*(2^20)超过百万,内存空间不够)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int n,m,k;
int dp[N][1000],w[22];
int main()
{
cin >> n >> m >> k;
int t;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= k; ++j)
{
cin >> t;
w[i] |= (1 << t-1);
}
}
memset(dp,0x3f,sizeof dp);
dp[0][0] = 0;
int all = (1 << m)-1;
for(int i = 1; i <= n; ++i)
{
for(int j = 0; j <= all; ++j)
{
dp[i][j] = min(dp[i-1][j],dp[i-1][j & (~w[i])]+1);
}
}
if(dp[n][all] >= 0x3f3f3f3f)
cout << -1 << endl;
else
cout << dp[n][all] << endl;
return 0;
}
状压
#include <iostream>
#include <cstring>
using namespace std;
const int N = (1<<20)+10;
int n,m,k;
int dp[N],w[22];
int main()
{
cin >> n >> m >> k;
int t;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= k; ++j)
{
cin >> t;
w[i] |= (1 << t-1);
}
}
memset(dp,0x3f,sizeof dp);
dp[0] = 0;
int all = (1 << m)-1;
for(int i = 1; i <= n; ++i)
for(int j = all; j >= 0; j--)
{
dp[j] = min(dp[j],dp[j & (~w[i])]+1);
}
if(dp[all] >= 0x3f3f3f3f)
cout << -1 << endl;
else
cout << dp[all] << endl;
return 0;
}
IDA*+步调函数
1.迭代加深:递归的层数从0到n逐步增加,在本题的意义为——选一包糖果可行吗,选两包呢,选三包呢,知道选到x包可行或大于m包代表无解
2.优先选择选择性最小的列:比如说仍有第一种糖果和第二种糖果没选,第一种糖果的选法有50种,第二种有30种,优先选择第二种可以更有效减少递归次数,因为第二种的选法肯定包含第二种糖果,同时那些共同包含第一种与第二种糖果的情况也包含在里面
3.步调函数:通过下边界求出至少需要多少包,比如说:我要选第一种糖果,就相当于把所有包含第一种糖果的情况都选了,且只算作一次
4.lowbit优化:lowbit可以返回当前数的最后一位1,正好可以帮助我们找出哪些糖果没被选到
5.可行性剪枝:通过步调函数,判断当前步数是否有解,若depth甚至比步调函数的最少步调少肯定无解直接返回
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 110, M = 1 << 20;
vector<int> col[30];
int log2[M];
int n, m, k;
//返回最右边1的位置,比如说:10100返回4(100)
int lowbit(int x)
{
return x & -x;
}
//步调函数:返回至少需要再走几步
int h(int state)
{
int res = 0;
for(int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
res ++ ;
for(auto row : col[c]) i &= ~row;
}
return res;
}
bool dfs(int depth, int state)
{
if(depth == 0 || h(state) > depth) return state == (1 << m) - 1;
//优先选择方案数最少的
int t = -1;
//减去当前状态就可以得知缺少哪一种糖果,对剩下的糖果取方案数最少的
for(int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
if( t == -1 || col[c].size() < col[t].size())
t = c;
}
for(int row : col[t])
if(dfs(depth-1,state | row))
return true;
return false;
}
int main()
{
cin >> n >> m >> k;
for(int i = 0; i < m; ++i) log2[1 << i] = i; //log2可以找出每个1的列数
for(int i = 0; i < n; ++i)
{
int state = 0;
for(int j = 0; j < k; ++j)
{
int c;
cin >> c;
state |= 1 << c - 1;
}
for(int j = 0; j < m; ++j)
if(state >> j & 1)
col[j].push_back(state);
}
//去掉重复的行
for(int i = 0; i < m; ++i)
{
sort(col[i].begin(),col[i].end());
col[i].erase(unique(col[i].begin(),col[i].end()),col[i].end());
}
int depth = 0;
while(depth <= m && !dfs(depth,0)) depth++;
if(depth > m) depth = -1;
cout << depth << endl;
return 0;
}
分类:
有趣的算法题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!