题目传送门
糖果店的老板一共有 M M M 种口味的糖果出售。为了方便描述,我们将 M M M 种口味编号 1 1 1 ∼ M M M。
小明希望能品尝到所有口味的糖果。遗憾的是老板并不单独出售糖果,而是 K K K 颗一包整包出售。
幸好糖果包装上注明了其中 K K K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。
给定 N N N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。
这是道入门的状态压缩DP的题目。
一个int类型的数据是四个字节,可以表示32位二进制数
一个long long类型的数据是八个字节,可以表示64位二进制数
二进制数只有01的不同,因此对于五位二进制数: 00000 – 11111 的不同组合就是 2^5 种,而我们便可以把每一种组合表示为一个状态,比如00001 00010,他们都是不同的状态。
我们把每一个糖果包中的 k颗糖果 表示为一个状态:例如:
如上,我们把每一个状态压缩为了二进制数字,其中00011表示了 这包糖果具有 1 1 2这个口味组合, 10110表示了这包糖果具有2 3 5这个口味组合。
那么题目让我们品尝到所有口味的糖果.
显而易见,我们就必须得到一个 11111 的口味组合的状态,使得 1 2 3 4 5 这m种口味都能够表示。
那么我们首先定义一个 kw[i]:存储给出的第 i i i 包,每包 k w [ i ] kw[i] kw[i] 颗糖果的口味状态
。
即我们转换为
我们定义dp数组:其中我们的dp数组应该能表示所有的口味状态,即 11111…111 有m个1,因此我们的dp数组应该足够大,对于题目中m最大为20,所以我们应该定义:
dp[1<<20] 使得最多可以表示20个1,因此能够存储所有的状态。
d p [ i ] dp[i] dp[i]:表示得到口味为 i i i 所需要的糖果包的最少的数量
状态转移:我们当前的口味组合为 i i i,则我们加入一包糖果,得到新的口味的组合为 j j j,则从 i i i 到 j j j 需要的糖果包的数量就是 d p [ i ] + 1 dp[i] +1 dp[i]+1,如果说已经已经更新过了 d p [ j ] dp[j] dp[j],即 j j j 种口味的糖果包的最少数量,则如果 d p [ j ] > d p [ i ] + 1 dp [j] >dp[i]+1 dp[j]>dp[i]+1,则我们将dp[j] 更新为这个较小的值。
状态转移公式为:
d p [ j ] = m i n ( d p [ j ] , d p [ i ] + 1 ) dp[j]=min(dp[j],dp[i]+1) dp[j]=min(dp[j],dp[i]+1)
#include
#include
#include
#include
using namespace std;
//TODO: Write code here
int n,m,k;
const int N=1e5+10;
int nums[N],dp[1<<20],kw[N];
signed main()
{
cin>>n>>m>>k;
/*
dp[i]表示口味为i时最少的糖果包的数量
*/
memset(dp,-1,sizeof(dp));
for (int i=1;i<=n;i++)
{
int temp=0;
for (int j=1;j<=k;j++)
{
int a;
cin>>a;
temp|=(1<<a-1);
/*
记录每一包糖果的口味状态
a=1: temp=00001
a=2: temp=00011
a=3: temp=00111
*/
}//temp表示第i包糖果的口味
kw[i]=temp; //第i包糖果的口味
dp[temp]=1; //口味为temp时需要的最少的糖果包的数量默认为1
}
for (int i=0;i<1<<m;i++)//遍历所有的口味组合的状态
{
if (dp[i]!=-1)//存在这种口味组合
{
for (int j=1;j<=n;j++)
{
int temp=kw[j];//获得每一包糖果的口味
//i|temp表示原来的i口味加上这包temp口味后得到的 新的口味
if (dp[i|temp]==-1){
dp[i|temp]=dp[i]+1;
}
dp[i|temp]=min(dp[i|temp],dp[i]+1);
}
}
}
cout<<dp[(1<<m)-1];//得到所有的口味的最少糖果数量
#define one 1
return 0;
}
题目传送门
在一个二维平面中,有 n n n 个坐标点。一个人 ( 0 , 0 ) (0,0) (0,0) ( 0 , 0 ) (0,0) (0,0) 点处出发去达所有点,问至少要走多少距离?
我们可以把整个地图看作一个 S S S 集合, S S S 集合包含原点以及所给的坐标点。
题目让我们要从S中的 ( 0 , 0 ) (0,0) (0,0) 开始走,能够到达所有点,求这个最短路径。
我们不妨设: d p [ S ] [ j ] dp [S] [j] dp[S][j]为 在集合S中走完所有的点,并且以j为最后的终点的最短距离
那么如果我们在 S S S 集合中把 这个点 j j j 去掉,即现在变成了 S − j S-j S−j 的集合,那么现在 S S S集合中就不包含 j j j 点,我们假设 d p [ S − j ] [ k ] dp[S-j][k] dp[S−j][k] 为在 S − j S-j S−j 集合中以 k k k 为终点的最短距离,那么只要我们得到了这个dp值,并且我们再加上 d i s ( j , k ) dis(j,k) dis(j,k) 之间的距离,因为两点之间的距离肯定是最短的,无法再分割了.
因此我们便可以在去掉j点的S集合中寻找一个k点,然后计算j点和k点的最短距离来得到最短的距离。
我们的状态转移方程如下:
d p [ S ] [ j ] = m i n ( d p [ S ] [ j ] , d p [ S − j ] [ k ] + d i s ( j , k ) ) dp[S][j]=min(dp[S][j],dp[S-j][k]+dis(j,k)) dp[S][j]=min(dp[S][j],dp[S−j][k]+dis(j,k))
状态转移方程的分析应该是比较简单的,那么我们考虑如何来表示 S集合??
我们使用二进制数的方式来表示,比如 S的集合可以表示为 11111,他的含义是所有的点都在S集合中,S-j的集合可以表示为 11011,他的含义是去掉了j点(用二进制0表示这个点)
实现的方式:
(S>>j)&1
:由这个式子便可以判断S集合中是否包含 j 点。S ^(1<:即可实现把 j 点从S集合中去除。
S ^ (1<> k &1
:即可实现在 S-j 集合中,通过枚举一个变量k,来实现遍历 S-j 中的所有的点,因为这些点在二进制中一定是 1,所以要 & 1我们的dp应该定义为 dp[1<<17] [20],因为 S的集合最多有15个点,所以我们的 第一维应该足够容纳 11111…1111等最多 15 个1,第二维是作为终点的点的个数
综上,我们首先for循环枚举整个S集合的所有可能的情况,然后for循环枚举 j ,表示要将 j 从S中移除,然后for循环枚举 k,利用k 来实现遍历 S-j 集合中的每一个点了。
最后我们来枚举一个最小值,我们 的 dp[S] [0]表示在集合S中到达终点0的最短路径, 我们通过一个for循环来遍历在S中所有的以 i 为终点的最短路径,即在所有的 dp[(1<//坐标搜索:旅行商问题
namespace test47
{
const int N = 1005;
int n;
double dp[1 << 17][20]; //表示地图的点的集合
double x[N], y[N];
double dis(int a, int b)
{
return sqrt((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]));
}
void test()
{
memset(dp, 0x7f, sizeof(dp));
cin >> n;//坐标点的数量
x[0] = 0, y[0] = 0;
for (int i = 1; i <= n; i++)
{
cin >> x[i] >> y[i];
}
++n; //原点(0,0)
/*
dp[i][j]表示在 i 集合中到达点 j 的最短路径长度
dp[i][j]=min(dp[i-j][k]+dis(j,k),dp[i][j]) 在i-j集合中到达k的最短路径长度+j与k的路径长度
*/
dp[1][0] = 0;//一开始S集合中只有(0,0),距离为0
for (int S = 0; S < (1 << n); S++)//遍历所有的地图集合
{
for (int j = 0; j < n; j++)//枚举点j,改变集合S为 S-j
{
for (int k = 0; k < n; k++)//枚举到达j的点k,k属于集合S-j
{
/*
1. 判断当前集合S中是否含有j点:(S<