1 条题解
-
0
做题时卡的最久的地方:发现 可过。
考虑如果我们不能把局面信息记录下来的话就尝试记录一些关键信息,什么信息是关键的?
考察局面形成的过程,最开始有一个长度为 的段,然后你在某个位置放入了一个物品,其分裂为两个段,以此类推。
不难发现局面形成的过程可以对应一棵树的生成过程,树上存在方点(物品)和圆点(空段),每个点有一个长度参数,父亲的参数是儿子参数和,我们的所有限制其实都是要求某些圆点的长度在一个区间内。
不难猜出树上每个圆点实际上的合法取值范围应当也是一个区间,可以通过归纳简单证明,区间下界显然是子树内方点长度和,考虑通过 dp 求出这个上界,这个 dp 的转移就是直接枚举子集。
注意到只有当确定了哪些点不能放时才确定了一些限制,在 dp 中处理哪些点不能放开销太大,考虑在外层枚举没放进去的物品集合 再对 的所有子集跑 dp 即可,时间复杂度 。
#include<bits/stdc++.h> using namespace std; #define int long long const int maxn = 14; const int maxv = 1<<14; int n,m; int sum[maxv]; int a[maxn]; int f[maxv]; set<int> ans; signed main(){ ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); cin>>m>>n; for(int i=0;i<n;i++) cin>>a[i],sum[1<<i]+=a[i]; for(int i=0;i<n;i++){ for(int j=0;j<(1<<n);j++){ if((1<<i)&j) sum[j]+=sum[j-(1<<i)]; } } for(int S=1;S<(1<<n)-1;S++){ //S 内的物品不选 for(int T=((1<<n)-1-S);T;T=(T-1)&((1<<n)-1-S)) f[T]=-1e18; f[0]=1e18; for(int i=0;i<n;i++){ if((1<<i)&S) f[0]=min(f[0],a[i]); } for(int i=0;i<n;i++){ if(!((1<<i)&S)){ f[1<<i]=1e18; for(int j=0;j<i;j++){ if((1<<j)&S) f[1<<i]=min(f[1<<i],a[j]); } for(int j=i+1;j<n;j++){ if((1<<j)&S) f[1<<i]=min(f[1<<i],a[i]+2*a[j]); } if(f[1<<i]<=sum[1<<i]) f[1<<i]=-1e18; } } for(int T=0;T<=(1<<n)-1-S;T++){ if((S&T)==0&&__builtin_popcount(T)>=2){ int root=-1; for(int i=0;i<n;i++){ if((1<<i)&T){ root=i; break; } } int t=T-(1<<root); for(int U=t;U;U=(U-1)&t){ f[T]=max(f[T],f[U]+f[t-U]+a[root]); } for(int i=0;i<n;i++){ if((1<<i)&S){ if(i<root) f[T]=min(f[T],a[i]); } } if(f[T]<=sum[T]) f[T]=-1e18; } } if(f[(1<<n)-1-S]>m&&sum[(1<<n)-1-S]<=m) ans.insert(sum[(1<<n)-1-S]); } //都不选 int mi=1e18; for(int i=0;i<n;i++) mi=min(mi,a[i]); if(mi>m) ans.insert(0); //都选 if(sum[(1<<n)-1]<=m) ans.insert(sum[(1<<n)-1]); cout<<ans.size()<<"\n"; for(int x:ans) cout<<x<<" "; return 0; }
- 1
信息
- ID
- 8982
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者