背包简单题选做

https://www.luogu.com.cn/problem/list?keyword=背包&tag=139&difficulty=7&page=1

[HNOI2007]梦幻岛宝珠

题目描述

点此看题

解法

多步 (dp) 往往最为致命。

这道题是不可能直接背包的,只有一个条件能用 (aleq10,bleq30) ,这决定了你能不能做出来这题。

有一个原则是把较小的东西放进状态中 (dp) ,上面那个条件提示我们可以按二进制数位考虑,(f[i][j]) 表示对于 (2^i) 的数位用了 (j) 个,那么花费的真实容量是 (2^i imes j) ,第二维的上界是 (10 imes n) 所以这个可以直接 (01) 背包。

然后合并这些数位就可以得到答案,这时候再做一个 (dp) ,设 (g[i][j]) 表示合并了前 (i) 个数位用掉了容量是 (2^i imes j)这个状态设计的原则还是放较小的东西 ,这一位用不完的容量可以留给下一位,所以就得到了方程:

[g[i][j]=max f[i][j-k]+g[i-1][min(10n,2k+bitm[i-1])] ]

那个 (10n) 就是表示这个上界就够用了,(bitm[i-1]) 容量在 (2^{i-1}) 这一位上是否有值,由于最后的答案是 (g[log-1][1]) 只考虑了最大的数位,所以其他的数位是要在 (dp) 过程中补上的((log) 表示 (m) 的二进制有多少位,它的最高位一定是 (1)

( t 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
",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
",f[(1<<t)-1]);
}

原文地址:https://www.cnblogs.com/C202044zxy/p/14392484.html