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;
}
posted @ 2025-04-14 22:54  KS_Fszha  阅读(13)  评论(0)    收藏  举报