hdu6092 Rikka with Subset (反向背包)

题意:

有一个 n 个正数的序列 A,和为 m 。现给出这个序列所有的子集和的出现次数(共2n个子集,但不同子集的和可能会相同), B[i] 表示有 B[i] 个子集的和是 i
求出序列 A 。

分析:

这道题可以正着推也可以倒着推。

正着推:

所谓正着推,就是一步步模拟出来 B。何为一步步模拟? B 的含义其实是,n 个元素的所有子集的和的出现的次数。就像背包问题中的 f[V] ,在第 i 次循环中表示只装前 i 个物品的最大值。 n 轮循环进行完了,f[V] 也就相当于是可装所有物品的最大值了。此处同理,我们用 f[s] 表示只考虑前 i 个数的所有子集时,子集和 s 出现的次数。很显然,被“考虑”数字最小是 1 最大是 m 。所以当 m 这轮循环也进行完后,f 就和 B 完全相同了。

那么,如何利用这个过程来求出 A 中有哪些数呢?假如现在进行到了第 i 个数,那 f[i] 存的是前 i1 个数字的所有子集中,有多少个子集和是 i 。如果 f[i]==B[i],说明 A 序列中没有 i 这个数字,若有的话 B[i] 应该比 f[i] 大,有几个 i 就大几。从 1 m ,每个数都这样判断一遍,就得出了 A 中有哪些数。

至此,思路彻底理清楚。那么如何更新 f 呢?就是背包的思想,想知道用上 i 这个数字后, f[k] 会有何变化 (k>=i ), i 想凑成 k 还需要 ki 。那么,有多少种凑 ki 的方法,就有多少种用上数字 i k 的方法。值得注意的是,数字 i 不一定只有 1 个,有几个就要进行几次 dp 的更新,因为在构成子集的时候,两个 i 是不一样的。

倒着退:

如果 Bi B 数组中除了 B0 以外第一个不为 0 的位置,那么显然,i 就是 A 中最小的元素。现在需要求出删除掉 i 之后的 B 数组。显然,i 只能影响和大于i的部分,所以对于这一部分,只需要从小到大让 Bj=Bji。至于为什么是从小到大,因为更新较大的数的时候,可能会用到较小的数,而这时我们必须保证这个较小的数是被更新过的,即这个较小的数中不能再出现 i <script type="math/tex" id="MathJax-Element-968">i</script>。

代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int MAXN=1e4+5;
const double EPS=1e-8;
const int INF=0x3f3f3f3f;
int b[MAXN],ans[MAXN],f[MAXN],n,m;
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=0;i<=m;i++){
            scanf("%d",&b[i]);
            f[i] = 0;
        }
        int tot = 0;
        f[0] = 1;
        for(int i=1;i<=m;i++){
            int num = b[i] - f[i];
            if(tot == n)    break;
            for(int j=0;j<num;j++){
                ans[tot++] = i;
                for(int k=m;k>=i;k--){
                    f[k] += f[k-i];
                }
            }
        }
        for(int i=0;i<n;i++){
            printf("%d%c",ans[i]," \n"[i==n-1]);
        }
    }

    return 0;
}
#include<bits/stdc++.h>  
using namespace std;  
#define pii pair<int, int>  
typedef long long ll;  
const int maxn = 10105;  
ll b[maxn];  
int ans[55];  
int main()  
{  
    int t, n, m;  
    scanf("%d", &t);  
    while(t--)  
    {  
        scanf("%d%d", &n, &m);  
        for(int i =  0; i <= m; i++)  
            scanf("%I64d", &b[i]);  
        int cnt = 0;  
        for(int i = 1; i <= m; i++)  
        {  
            if(b[i] == 0) continue;  
            if(cnt == n) break;  
            ans[cnt++] = i;  
            for(int j = i; j <= m; j++)  
                b[j] -= b[j-i];  
            i--;  
        }  
        for(int i = 0; i < n; i++)  
            printf("%d%c", ans[i], i == n-1 ? '\n' : ' ');  
    }  
    return 0;  
}  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值