Luogu P3052 [USACO12MAR]摩天大楼里的奶牛Cows in a Skyscraper|DP
题意(搬运自luogu):给出\(n\)个物品,体积为\(w_i\),现把其分成若干组,要求每组总体积$ \le W$,问最小分组。
( $ n \le 18 $, $ 1 \le W \le 1\times 10^8 $,\(1\le 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;
}