题意(搬运自luogu):给出(n)个物品,体积为(w_i),现把其分成若干组,要求每组总体积$ le W$,问最小分组。
( $ n le 18 $, $ 1 le W le 1 imes 10^8 $,(1le c le w))
看起来本题无从下手,所以我们可以先用(dfs),我们设((x,y,z))的意义是当前选择状态为(x),当前分了(y)组,最后一组还剩(z)空间。这个状态中,我们并不需要考虑将一个物品放在前面的组中,因为这个等同于在该状态前面就放入了这个物品。则我们在每轮(dfs)中枚举放哪一个物品到最后一组(或新开一组)。
这种做法看起来不太好,我们尝试优化。
我们可以证明在(x,y)相同时,对于(z)和(z'),若(z>z'),((x,y,z))推出来的答案一定不会比((x,y,z'))更劣,那我们就不需要(dfs) ((x,y,z'))的状态了。
因此,我们记录每个((x,y))中的最小(z),若(z'>z)就不(dfs (x,y,z'))。
与此同时,我们在(dfs)中也不需要记录(z)了。因为(z)一定是我们记下的最小的(z)。
然而这样就过了???
#include<bits/stdc++.h>
using namespace std;
int n,m,a[100],ans=2147483647;
int f[262147][20];//记录(x,y,z)中z的最小值,大于则不用再搜
void dfs(int x,int y)
{
if (x==(1<<n)-1) ans=min(ans,y);//结束状态
int z=f[x][y];
for (int i=1;i<=n;i++)
{
if (!((x>>(i-1))&1))//当前位没放入任意一组
{
int newx=x+(1<<(i-1));
if (z>=a[i])//放入当前组
{
if (z-a[i]>f[newx][y])//优化
{
f[newx][y]=z-a[i];
dfs(newx,y);
}
}
else//新开一组
{
if (m-a[i]>f[newx][y+1])//优化
{
f[newx][y+1]=m-a[i];
dfs(newx,y+1);
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=0;i<(1<<n);i++)
for (int j=0;j<=n;j++)
f[i][j]=-1;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
dfs(0,0);
cout<<ans<<endl;
return 0;
}
原来只要把y按同样方法做就可以加速了
按上面的逻辑,显然(x)相同时,(y)越小越好,则我们可以再次优化。
贴上再次优化后代码
//除优化了y,其余相同
#include<bits/stdc++.h>
using namespace std;
int n,m,a[100],ans=2147483647;
int f[262147],f1[262147];//f1表示相同状态下最小的y
void dfs(int x)
{
int y=f1[x];int z=f[x];
if (x==(1<<n)-1) ans=min(ans,y);
for (int i=1;i<=n;i++)
{
if (!((x>>(i-1))&1))
{
int newx=x+(1<<(i-1));
if (z>=a[i])
{
if (y<=f1[newx])
{
if (y<f1[newx]) f[newx]=-1;
f1[newx]=y;
if (z-a[i]>f[newx])
{
f[newx]=z-a[i];
dfs(newx);
}
}
}
else
{
if (y+1<=f1[newx])
{
if (y+1<f1[newx]) f[newx]=-1;
f1[newx]=y+1;
if (m-a[i]>f[newx])
{
f[newx]=m-a[i];
dfs(newx);
}
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=0;i<(1<<n);i++)
f1[i]=2147483647,f[i]=-1;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
f[0]=-1;f1[0]=0;
dfs(0);
cout<<ans<<endl;
return 0;
}