Nowcoder 104637 E 小苯的 Polygon 题解 [ 蓝 ] [ 背包 dp ] [ bitset 优化 ] [ adhoc ]
小苯的 Polygon:不错的 bitset 优化 dp 题。
观察
首先我们来观察,满足什么性质的边能组成一个凸多边形。
有一个显然的结论:如果一种方案能组成凹多边形,那么他也一定能组成凸多边形。原因很显然,因为你把凹进去的部分往外翻就可以了。
那么什么样的方案能组成多边形呢?考虑类比三角形,我们找出多边形里最长的一条边,如果其他边的长度之和大于这条最长边,那么就一定能组成多边形。
画个图基本就能猜出来这个结论了,这个观察不是很难,但我不太会证。大致思路应该就是钦定完这条最长边时候,在他外边套个圈,长度一定比他长吧。
朴素 dp
考虑如何做求出最小周长。一个很 naive 的思路是枚举最长边,然后看所有比它短的边能组成比他大的最小总和是多少。
然后你发现值域是 \(100\),总和最多也就 \(10^4\),然后就做完了???
直接定义 \(dp_{i,j}\) 表示钦定第 \(i\) 小的木棒为最长边,用小于等于它的木棒能否拼出 \(j\) 来。
然后背包转移即可。
每次扫一遍 dp 数组,同时这个背包显然是可以滚动数组把第一位滚掉的。
时间复杂度 \(O(tnV)\),可以通过,但是不够优秀。
bitset 优化
因为这个是 \(01\) 数组上的 dp,同时还是位移转移,所以考虑 bitset 优化。
在做背包的时候,我们直接用 bitset 存 dp 数组,转移的时候把 dp 左移 \(a_i\) 位然后与原来的 dp 数组去一个并集就好了。
然后查找 \(a_i\) 后面第一个是 \(1\) 的位置,用 _Find_nect(pos) 即可,作用是找到 bitset 中下标严格大于 \(pos\) 的最小为 \(1\) 的位置的下标;如果找不到就会返回 bitset 的长度。更多的 bitset 函数详见 OI-wiki-bitset。
时间复杂度 \(O(tn\frac{V}{w})\),跑的飞快。
代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int V=10005,N=105,inf=0x3f3f3f3f;
int n,a[N],ans;
bitset<V>dp;
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
ans=inf;
dp.reset();
dp[0]=1;
for(int i=1;i<=n;i++)
{
int pos=dp._Find_next(a[i]);
if(pos<=10000)ans=min(ans,pos+a[i]);
dp=(dp|(dp<<a[i]));
}
if(ans>=inf)cout<<-1<<'\n';
else cout<<ans<<'\n';
}
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int sid;
cin>>sid;
while(sid--)solve();
return 0;
}

浙公网安备 33010602011771号