题解 选数
给出 n 个数,要求从中选出任意个数,使之能划分为和相等的两组,求方案数。
.
一道非常不错的”折半“搜索。
注意:同样一组选数,划分方法不同不会重复计数。
“划分为和相等的两组” 可以进行一定的转化,比如:对于数列:,我们可以将其划分为 ,但也可以这么想:将 乘上权值 ,还是 ; 乘上权值 ,变成 ,而 。
所以检验一个选数方案是否合法,可以看成能否附上权值 使得和为 。
如果直接暴力,那么每个数有 不选(赋),选并赋,选并赋,时间复杂度为 ,无法通过本题。
由于我们关心到底有哪些数选了,所以可以维护一个有哪些数字没选的状态,二者等价。
考虑折半搜索,假设左边选 个数,可以在 内计算出所有和及其对应状态。
对于右边,我们只需处理 个数,同样可在 内计算出和及对应的状态。
假设现在计算出的和为 , 此时统计答案需要考虑:
左边有多少个状态出现了 ,以及左边的状态和右边状态合起来,会不会在以前出现过,如果出现过,就说明我们之前考虑过这个选数方案,不能重复计数。
所以直接暴力枚举左边的状态,时复 ,所以总时复 ,通过计算,对于极限数据 最优。
但是直接暴力枚举常数过大,无法通过本题,由于我们需要快速处理左边出现的状态与不会与当前状态并起来出现过的状态的并集的个数,考虑 优化。
记:zt_num[i]
表示左边凑出和为 的状态情况,zt_st[i]
表示对于右边的状态 ,有哪些状态可以和他合在一起(即合在一起的状态之前没有出现过)。
那么可以这么快速统计答案:
ans+=(zt_sum[-nsum]&(~zt_st[zt])).count(); zt_st[zt]|=zt_sum[-nsum]&(~zt_st[zt]);
代码解释:
由于 zt_st[zt]
中等于 的位置是之前没有出现过的,即合法的,所以在 的时候需要进行取反。
zt_sum[-nsum]&(~zt_st[zt])
即求出上述说到的并集。
zt_st[zt]|=zt_sum[-nsum]&(~zt_st[zt]);
将本次统计的状态进行标记,以后不在统计。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long LL; #define PLI pair<LL,int> const int N=26; int n,len1,ans,a[N]; unordered_map<LL,bitset<1<<17>> zt_sum,zt_st; void dfs(int nd,int ed,LL nsum,int zt) { if(nd==ed+1) { if(ed!=n) { zt_sum[nsum][zt]=1; } else { ans+=(zt_sum[-nsum]&(~zt_st[zt])).count(); zt_st[zt]|=zt_sum[-nsum]&(~zt_st[zt]); } return ; } dfs(nd+1,ed,nsum,zt|(1<<nd-1)); dfs(nd+1,ed,nsum+a[nd],zt); dfs(nd+1,ed,nsum-a[nd],zt); } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; len1=n*16/23; dfs(1,len1,0,0); dfs(len1+1,n,0,0); cout<<ans-1; //代码会统计啥也不选的情况,减掉 return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2023-07-12 题解 醋溜便当