• 背包简单题选做


    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]);
    }
    
    
  • 相关阅读:
    LeetCode 24. Swap Nodes in Pairs
    LeetCode 02.07. Intersection of Two Linked Lists LCCI
    LeetCode 707. Design Linked List
    centos7 mail
    centos7 安装postgresql10
    centos 7 安装 mail
    解决运行docker命令要用sudo的问题
    Linux 命令速记本
    iterm2配置lrzsz
    截取某段时间内的日志
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14392484.html
Copyright © 2020-2023  润新知