Game Bundles 题解
题目链接
分析
很神奇的一道题目
先想想如何计算一个集合和为60的子集个数
可以想到通过 \(DP\) 求解:
记 \(f[i][j]\) 为前 \(i\) 个数字,和为 \(j\) 的子集个数
则
\(f[i][j]+=f[i-1][j-a[i]]\)
\(f[i][a[i]]++\)
可以倒序枚举 \(j\) 滚掉 \(i\) 这一维
代码如下
void sol(){
for(int i=1;i<=n;i++){
for(int j=60;j>=a[i]+1;j--) f[j]+=f[j-a[i]];
f[a[i]]++;
}
}
易发现,对于和为 \(60\) 的子集,其中值大于 \(30\) 的数最多有一个
也就是说,若在原集合中加入一个大于 \(30\) 的数 \(a\) , 则 \(f[60]+=f[60-a]\)
然后开始乱搞,
先随机一堆 \(0\) 到 \(60\) 的数,使其 \(f[60]\) 接近但不超过 \(m\)
然后将 \(f[1]\) ~ \(f[29]\) 从大到小排序,类似二进制从大到小选择,选择 \(f[i]\) 即加入 \(60-i\)
交上去发现 \(TLE\) 了,试几个较大的数,发现前面随的数的个数大多为上限 \(60\)
不难发现,加入的数较小 \(f[60]\) 增长速度要快,所以将随机数的上限改为 \(15\) ,然后就过了
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int NN=66,N=61;
int m,cnt=0,a[NN],f[NN];
struct F{int id,w;}f1[NN];//f数组的copy,用于排序
bool cmp(F a,F b){return a.w>b.w;}
bool sol(){
cnt=0;
while(cnt<60&&f[60]<m){
a[++cnt]=rand()%15+1;//随机1~15的数
for(int i=60;i>=a[cnt]+1;i--) f[i]+=f[i-a[cnt]];
f[a[cnt]]++;
}
f[a[cnt]]--;
for(int i=a[cnt]+1;i<=60;i++) f[i]-=f[i-a[cnt]];
cnt--;
//贪心选择
for(int i=1;i<=29;i++) f1[i].w=f[i],f1[i].id=i;
sort(f1+1,f1+30,cmp);
for(int i=1;i<=29;i++){
int d=f1[i].id,al=f1[i].w;
if(f[60]+al<=m&&cnt<60) a[++cnt]=60-d,f[60]+=al;
}
//判断答案
if(f[60]==m) return 1;
else return 0;
}
signed main(){
srand(time(0));
scanf("%lld", &m);
while(!sol()) for(int i=1;i<=N;i++) f[i]=0,a[i]=0;
cout<<cnt<<endl;
for(int i=1;i<=cnt;i++) cout<<a[i]<<" ";
return 0;
}