HDU 6092 Rikka with Subset(题解解释)

本文介绍了一种利用背包思想解决特定子集计数问题的方法,通过逐步消去影响更新计数数组,最终求得字典序最小的整数集合。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Link:http://acm.hdu.edu.cn/showproblem.php?pid=6092

题意:给定 n m b0 ~ bm ,要求求出集合A: a1 ~ an ,集合A中每个子集的和i的个数为 bi ,输出字典序最小的A。

完全没有想明白是一个背包思想的题,所以就着题解来理解一下这个题的思路。
首先对于给定b数组,我们每次寻找到除了 b0 以外最小的一个不为0的数i,这个数的数字一定是a中确定的数,然后每次找到i后,删去这个数对于整个b数组的影响。
如第二个样例

B: 1 3 3 1
选定i = 1删去1后:(1加入A数组)
对于 b1 ,则减少了一个1 因此变为 b1=b1b0=2
对于 b2 ,2可以由已经上一步中的 b1+1 得到,所以相当于要减去整个 b1 带来的影响,因此 b2=b2b1=1
对于 b3 ,3可以有上一步的 b2+1 得到,所以要减去整个 b2 带来的影响,因此 b3=b3b2=0
b3 的处理过程中,我们毋须再考虑是否会有1 1 1这种情况导致的3,因为上一步的2中就已经整个包括了1 1 和 2这种情况,删去影响时删去整个的 b2 即可。

此时B:1 2 1 0(刚好是A : 1 1时的B数组)
选定i = 1删去1:(1加入A数组)
对于 b1 ,则减少了一个1 因此变为 b1=b1b0=1
对于 b2 ,与之前的想法相同, b2=b2b1=0

此时B:1 1 0 0(为A:1时的B数组)
选定i = 1 删去 1:(1加入A数组)
修改 b1 ,则减少了一个1 因此变为 b1=b1b0=0
后面的修改后依旧为0
此时A数组中刚好有n个数,并且B数组除 b0 外全为0,无法继续修改,因此输出结果:1 1 1

对于第一个样例

B: 1 1 1 1
选定i = 1删去1后:(1加入A数组)
对于 b1 ,则减少了一个1 因此变为 b1=b1b0=0
对于 b2 b2=b2b1=1
对于 b3 b3=b3b2=0

此时B:1 0 1 0(刚好是A : 2 时的B数组)
选定i = 2删去2:(2加入A数组)
对于 b2 ,则减少一个2 因为此时的 b2=b2b0=0
对于 b3 ,因为这个 b3 我们在之前的一步中已经考虑过1对它的影响,这个则只需要考虑2的影响,又因为2+1=3,所以如果要减去2对3的影响,就是 b3 减去 b1 的影响,因此 b3=b3b1=0
此时A数组中刚好有n个数,并且B数组除 b0 外全为0,无法继续修改,因此输出结果:1 2

这个消去影响的思想主要是一种背包的思想,即每次只选择去拿一个数,考虑这个数对它之后的数会产生的影响。
对于一个3,第一次因为2+1=3,那么对于它来说,1个1的影响是由2产生的,则减去2的数量即可;第二次1+2=3,对于它来说一个2的影响只会是1产生的,则减去1的数量即可。那么这里会不会影响重复呢?答案是不会,因为第二次的时候,1的数量是肯定为0的,这样会保证不重复减。

如何和背包的思想结合还是等我看了翻货币问题再说说看吧~
贴代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const int N = 1E4 + 10;
typedef long long ll;
ll b[N];
ll a[55];
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        int n, m;
        scanf("%d %d", &n, &m);
        for(int i = 0; i <= m; i++){
            scanf("%lld", &b[i]);
        }
        int cnt = 0;
        for(ll i = 1; i <= m; i++){
            if(b[i]){
                if(cnt == n) break;
                int num = b[i];
                for(ll j = 0; j < num; j++){
                    a[cnt++] = i;
                    for(ll k = i; k <= m; k++){
                        b[k] -= b[k-i];
                    }
                }
            }


        }
        for(int i = 0; i < cnt; i++){
            if(i == cnt - 1) printf("%lld\n", a[i]);
            else printf("%lld ", a[i]);
        }


    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值