【BZOJ1110】[POI2007] 砝码(进制分解)
大致题意: 有\(n\)个背包和\(m\)个砝码,每个背包有一定的重量限制,而砝码两两之间重量都成倍数关系。求最多能把几个砝码装入背包。
前言
\(Jan\ 29th\)刷题计划(5/6),算法标签:贪心。
一道有技巧的贪心,一道挺有趣的题目。
进制分解
考虑题目中“砝码两两之间重量都成倍数关系”这句话肯定有猫腻,必然要以它为入手点。
将砝码按重量排序并去重后,易知每一个砝码重量至少是前一个砝码的两倍,因此砝码重量的种数总共也只有\(log_2(10^9)≈30\)种,是非常少的。
设排序去重后第\(i\)种砝码重量为\(a_i\),个数为\(b_i\)。
我们定义一个特殊的进制,其中第\(i\)位的位权是\(a_i\)。
然后我们把背包的容量都按照这种特殊的进制进行进制分解,然后分每一位加在一起。注意,加的过程中不能进位,因为每个背包是独立的。
最后,我们贪心地从小到大枚举每一种砝码。
对于第\(i\)种砝码,我们操作\(b_i\)次,每次将第\(i\)位减\(1\)。若减成了负数,则去向更高位借位。若无法再借,就说明无法再加砝码。
注意,第\(i+1\)位的\(1\)退到第\(i\)位,会变成\(\frac{a_{i+1}}{a_i}\)。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,m,k,s[N+5],a[N+5],b[N+5],p[N+5];
I bool Dec(CI x)//将第x位减1
{
RI i;for(i=x;i<=k&&!p[i];++i);if(i>k) return 0;//如果借不到,返回0
for(--p[i--];i>=x;--i) p[i]+=a[i+1]/a[i]-1;return 1;//借位,返回1
}
int main()
{
RI i,j,t=0;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",s+i);
for(i=1;i<=m;++i) scanf("%d",a+i);sort(a+1,a+m+1);//排序
for(i=1;i<=m;++i) a[i]^a[i-1]?(a[++k]=a[i],b[k]=1):(++b[k]);//去重,同时统计每种砝码的个数
for(i=1;i<=n;++i) for(j=k;j;--j) p[j]+=s[i]/a[j],s[i]%=a[j];//进制分解
for(i=1;i<=k;++i) for(j=1;j<=b[i];++j) if(Dec(i)) ++t;else goto End;//枚举砝码尝试放入
End:return printf("%d",t),0;//输出答案
}
待到再迷茫时回头望,所有脚印会发出光芒