背包简单题选做
https://www.luogu.com.cn/problem/list?keyword=背包&tag=139&difficulty=7&page=1
[HNOI2007]梦幻岛宝珠
题目描述
解法
多步 \(dp\) 往往最为致命。
这道题是不可能直接背包的,只有一个条件能用 \(a\leq10,b\leq30\) ,这决定了你能不能做出来这题。
有一个原则是把较小的东西放进状态中 \(dp\) ,上面那个条件提示我们可以按二进制数位考虑,\(f[i][j]\) 表示对于 \(2^i\) 的数位用了 \(j\) 个,那么花费的真实容量是 \(2^i\times j\) ,第二维的上界是 \(10\times n\) 所以这个可以直接 \(01\) 背包。
然后合并这些数位就可以得到答案,这时候再做一个 \(dp\) ,设 \(g[i][j]\) 表示合并了前 \(i\) 个数位用掉了容量是 \(2^i\times j\) ,这个状态设计的原则还是放较小的东西 ,这一位用不完的容量可以留给下一位,所以就得到了方程:
那个 \(10n\) 就是表示这个上界就够用了,\(bitm[i-1]\) 容量在 \(2^{i-1}\) 这一位上是否有值,由于最后的答案是 \(g[log-1][1]\) 只考虑了最大的数位,所以其他的数位是要在 \(dp\) 过程中补上的(\(log\) 表示 \(m\) 的二进制有多少位,它的最高位一定是 \(1\))
\(\tt UPD2021/10/26\):可以直接从高位到低位考虑,那么只用留下 \(10n\) 的容量足够,所以时间复杂度正确。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 1005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,x,lg,f[35][M],g[35][M],pw[35];
void solve()
{
pw[0]=1;lg=0;x=m;
while(x>0) x>>=1,lg++;
for(int i=1;i<=30;i++)
pw[i]=pw[i-1]*2;
for(int i=1;i<=n;i++)
{
int w=read(),v=read();
for(int j=0;j<=30;j++)
if(w%pw[j]==0 && w/pw[j]<=10)
{
int a=w/pw[j];
for(int k=10*n;k>=a;k--)
f[j][k]=max(f[j][k],f[j][k-a]+v);
break;
}
}
for(int j=1;j<=10*n;j++) g[0][j]=f[0][j];
for(int i=1;i<=lg;i++)
{
for(int j=0;j<=10*n;j++)
for(int k=0;k<=j;k++)
g[i][j]=max(g[i][j],f[i][j-k]
+g[i-1][min(10*n,2*k+((m>>i-1)&1))]);
}
printf("%d\n",g[lg-1][1]);
}
signed main()
{
while(~scanf("%d %d",&n,&m))
{
if(n==-1 && m==-1) break;
memset(f,0,sizeof f);
memset(g,0,sizeof g);
solve();
}
}
星空
题目描述
解法
看到这种题就直接想到差分了,这样我们就能把区间修改变成单点修改。
关注到题目的关键条件是没点亮的灯泡数量是很少的,我们设 \(1\) 表示没点亮的灯泡,我们考虑 \(1\) 对差分数组的影响,让差分数组的长度为 \(n+1\) ,\(b[i]=a[i]\oplus a[i-1]\) ,那么由于最后一位是 \(0\) ,变成 \(1\) 之后一定会变成 \(0\) ,所以差分数组的 \(1\) 的个数是偶数,且小于等于 \(16\)
现在要消去这些 \(1\) ,由于消去的操作是异或两个位置,那么可以把 \(1\) 两两消去,发现消去的代价之和距离有关,我们把每种长度拆成 \(x\) 和 \(-x\) 然后去做完全背包,就可以算出消去的代价了。
然后由于 \(1\) 的个数很少搞个状压就行了,如果 \(1\) 的个数较大的话应该可以写最大权完美匹配。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 40005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,k,m,t,a[M],b[M],c[M],dp[M],f[1<<16];
signed main()
{
n=read();k=read();m=read();
for(int i=1;i<=k;i++)
a[read()]=1;
for(int i=1;i<=n+1;i++)
if(a[i-1]^a[i])
b[++t]=i;
for(int i=1;i<=m;i++)
c[i]=read();
memset(dp,0x3f,sizeof dp);
memset(f,0x3f,sizeof f);
dp[0]=f[0]=0;
for(int i=1;i<=m;i++)
for(int j=c[i];j<=n;j++)
dp[j]=min(dp[j],dp[j-c[i]]+1);
for(int i=1;i<=m;i++)
for(int j=n-c[i];j>=0;j--)
dp[j]=min(dp[j],dp[j+c[i]]+1);
for(int i=1;i<(1<<t);i++)
{
for(int j=0;j<t;j++)
for(int k=j+1;k<t;k++)
if((i&(1<<j)) && (i&(1<<k)))
{
int p=i-(1<<j)-(1<<k);
f[i]=min(f[i],f[p]+dp[b[k+1]-b[j+1]]);
}
}
printf("%d\n",f[(1<<t)-1]);
}