hdu6092 Rikka with Subset (反向背包)

来源:互联网 发布:js比较字符串 编辑:程序博客网 时间:2024/05/22 11:48

题意:

有一个 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 就大几。从 1m ,每个数都这样判断一遍,就得出了 A 中有哪些数。

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

倒着退:

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

代码:

#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;  }  
原创粉丝点击