折半搜索学习笔记
1. 思想
先搜前一半的状态,再搜后一半状态,最后记录两边状态并结合求解答案
通常,暴力搜索的时间复杂度是
如何判断一道题是否用折半搜索,一般情况下,n较小,答案较大
如果将搜索过程看做一张图,该算法则用于求图上A到B,长度为L的路径条数
2. 例题
2.1. 世界冰球锦标赛
分别对前20个和后20个求值,然后对前一半的统计结果排序,最后二分求解即可
代码:
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,l[2],idx[2];
ll m,a[2][45],ans[2][1048600],f[1048600];
void dfs(int id,int x,ll val)
{
if(val>m) return;
if(x>l[id])
{
ans[id][++idx[id]]=val;
return;
}
dfs(id,x+1,val+a[id][x]);
dfs(id,x+1,val);
}
int main()
{
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[0][i]);
}
l[0]=n/2;
l[1]=n-l[0];
for(int i=l[0]+1,j=1;i<=n;i++)
{
a[1][j]=a[0][i];
j++;
}
dfs(0,1,0);
dfs(1,1,0);
for(int i=1;i<=idx[0];i++)
{
f[i]=ans[0][i];
}
sort(f+1,f+idx[0]+1);
ll res=0;
for(int i=1;i<=idx[1];i++)
{
ll tmp=m-ans[1][i];
int x=upper_bound(f+1,f+idx[0],tmp)-f;
if(f[x]>tmp) x--;
// printf("%d %d\n",x,ans[1][i]);
res=res+x;
}
printf("%lld",res);
return 0;
}
2.2. [USACO12OPEN] Balanced Cow Subsets G
本题难点在于合并和去重
首先,可以分为在第一组,在第二组和不放三种情况,时间复杂度
考虑优化,可以通过折半搜索降低复杂度,即两边分别求解再合并
可以发现,如果两边的分法能结合,记两组分别为A,B,则必然存在
即:
接着考虑如何去重,利用状压的思想,可以用二进制记录是否取,最后统计答案即可
代码:
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#define ll long long
using namespace std;
int n,a[25],b[25],l1,l2,idx;
bool state[1048600];
map<ll,int> mp;
vector<int>v[60005];
void dfs1(int id,ll sum,int st)
{
if(id>l1)
{
if(!mp[sum]) mp[sum]=++idx;
v[mp[sum]].push_back(st);
return;
}
dfs1(id+1,sum+a[id],st<<1|1);
dfs1(id+1,sum-a[id],st<<1|1);
dfs1(id+1,sum,st<<1);
}
void dfs2(int id,ll sum,int st)
{
if(id>l2)
{
int x=mp[sum];
for(int i=0;i<v[x].size();i++)
{
state[(v[x][i]<<l2)|st]=1;
}
return ;
}
dfs2(id+1,sum+b[id],st<<1|1);
dfs2(id+1,sum-b[id],st<<1|1);
dfs2(id+1,sum,st<<1);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
l1=n/2,l2=n-l1;
for(int i=l1+1,j=1;i<=n;i++)
{
b[j]=a[i];
j++;
}
dfs1(1,0,0);
dfs2(1,0,0);
int ans=0;
for(int i=1;i<(1<<n);i++) ans+=state[i];
printf("%d",ans);
return 0;
}
[USACO09NOV] Lights G
可以枚举每个节点的状态,然后取最小值,时间复杂度
考虑优化,可以从中间拆开,分别搜索,将结果压成二进制数
前一半用map存储,后一半与1<<n-1异或后直接统计即可
注意细节和long long,数组最好都开成40,不然会寄的很惨!!!
代码:
#include<cstdio>
#include<map>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,head[40],edgenum;
struct edge{
int to,nxt;
}e[1205];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int l1,l2;
bool st1[40],st2[40],ed1[40],ed2[40];
map<ll,int>mp;
ll get_result1()
{
ll state=0;
for(int i=1;i<=n;i++)
{
ed1[i]=st1[i];
for(int j=head[i];j;j=e[j].nxt)
{
ed1[i]^=st1[e[j].to];
}
}
for(int i=1;i<=n;i++)
{
state=(state<<1)+ed1[i];
}
return state;
}
ll get_result2()
{
ll state=0;
for(int i=1;i<=n;i++)
{
ed2[i]=st2[i];
for(int j=head[i];j;j=e[j].nxt)
{
ed2[i]^=st2[e[j].to];
}
}
for(int i=1;i<=n;i++)
{
state=(state<<1)+ed2[i];
}
return state;
}
void dfs1(int x,int y)
{
if(x>l1)
{
ll state=get_result1();
// printf("%lld\n",state);
if(!mp[state]) mp[state]=y+1;
else mp[state]=min(mp[state],y+1);
return;
}
st1[x]=1;
dfs1(x+1,y+1);
st1[x]=0;
dfs1(x+1,y);
}
int ans=1e9;
ll cnt;
void dfs2(int x,int y)
{
if(x>n)
{
ll state=get_result2();
ll tmp=cnt^state;
//printf("%lld\n",state);
if(mp[tmp])
ans=min(ans,mp[tmp]+y-1);
return;
}
st2[x]=1;
dfs2(x+1,y+1);
st2[x]=0;
dfs2(x+1,y);
}
int main()
{
scanf("%d%d",&n,&m);
cnt=1ll*(1ll<<n)-1;
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
l1=n/2;
dfs1(1,0);
dfs2(l1+1,0);
printf("%d",ans);
return 0;
}
分类:
学习笔记 / 杂项
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南