题目地址:
http://acm.tju.edu.cn/toj/showp4168.html
题目概述:
给出一个集合以及一个m,集合中一开始有n个数,要求加入最少的数使得这个集合的各个子集的和能够覆盖1~m。
例如集合A={1,3},m=6,这时只需要将2加入A中,A的所有子集为[1], [2], [3], [1,2], [1,3], [2,3], [1,2,3],他们的和分别是1,2,3,3,4,5,6,能够覆盖1~6这个区间,所以答案为1。
大致思路:
一开始想到二进制位上去了,觉得每个数的二进制位都有个贡献之类的,结果还是too naive啊,跟正解差的太多。
首先对子集A的元素排个序,然后假设当前子集中最大的数为a[i-1],能够覆盖的范围是1~mx,那么如果a[i]>mx+1的话,必须要插入mx+1这个数才能使覆盖的范围仍旧连续,而插入mx+1,会使得覆盖的范围变成2*mx+1,此时再比较a[i]与当前的mx的大小,直到a[i]<=mx+1时加入a[i]会使得覆盖的范围变成mx+a[i],一直这么考虑下去就ok。
复杂度分析:
首先对于数m来说,最多只需要log2(m)个数就可以满足题意了,那么插入的复杂度应该就是O(n+logm),之前还需要排序,就是O(nlogn),总的的复杂度即O(nlogn)了。
代码:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <vector> #include <ctime> #include <map> #include <assert.h> #include <stack> #include <set> #include <queue> #include <cstring> #include <algorithm> using namespace std; #define sacnf scanf #define scnaf scanf #define maxn 100010 #define maxm 20010 #define inf 1061109567 #define INF 0x3f3f3f3f #define Eps 0.000001 const double PI=acos(-1.0); #define mod 1000000007 #define MAXNUM 10000 #define For(i,j,k) for(int (i)=(j);(i)<=(k);(i)++) #define mes(a,b) memset((a),(b),sizeof(a)) typedef long long ll; typedef unsigned long long ulld; void Swap(int &a,int &b) {int t=a;a=b;b=t;} ll Abs(ll x) {return (x<0)?-x:x;} int a[maxn]; int main() { //freopen("data.in","r",stdin); //freopen("data.out","w",stdout); //clock_t st=clock(); int T;scanf("%d",&T); while(T--) { int n;ll m;scanf("%d%lld",&n,&m); For(i,1,n) scanf("%d",&a[i]); sort(a+1,a+1+n);ll ms=0;int cnt=0; for(int i=1;i<=n;i++) { if(ms>=m) break; while(a[i]>ms+1&&ms<m) cnt++,ms=ms*2+1; ms=ms+a[i]; } while(ms<m) cnt++,ms=ms*2+1; printf("%d ",cnt); } //clock_t ed=clock(); //printf(" Time Used : %.5lf Ms. ",(double)(ed-st)/CLOCKS_PER_SEC); return 0; }