折半搜索——另类的二分法

折半搜索

前置知识

笼统的二分
\(dfs\)
清醒的头脑

思路

对于一些很暴力的爆搜,我们发现如果使用通常的搜索会炸。会发现,我们通常的搜索有很多状态是冗余的根本不可能实现的,但是又不得不去搜索,所以说,整体二分可以使这些无法实现的冗余状态少搜一点。

\(\color{Fuchsia}{同时,整体二分之后,对于之后的更快的搜索才是更重要的,而不是整体二分的过程。}\)

网上找到一个好图

典型例题

传送门

洛谷 P4799 [CEOI2015 Day2]世界冰球锦标赛

作为一道普及组入门题,我也觉得不过分,但是再看一眼数据 \(N \leq 40\) ,众所周知,这题普通的爆搜时间复杂度是 \(\mathbf{O}(2^N)\) ,明显是不行的,那么应该怎么做呢?

他这个题是要求,选出一些数,然后他们的总和不可以超过 \(M\) ,挺套路的二分。

我现在把他分成两堆数,比如说样例的 \(100\quad1500\quad500\quad500\quad1000\)

现在就变成了

\(100\quad1500\)
\(500\quad500\quad1000\)

那分成这样有什么用呢?
我现在对每一堆数据分别做 \(\mathbf{O}(2^N)\) 的爆搜,然后找出他们可以组成多少钱的门票。
对于第二堆数排序。
然后对第一堆数一个一个的分析,二分在第二堆数中找到恰好合法的那个地方,前面的必定全部合法,那么这题就挺简单了啦。

#include <bits/stdc++.h>
#define debug puts("I ak IOI several times");
#define pb push_back
#define int long long
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template <typename T>inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
long long n,a[50],b[50],na,nb;
long long posa[1<<21],posb[1<<21],m;
long long cnta,cntb;
void dfs1(long long dep,long long sum){
	if(dep>na){
		posa[++cnta]=sum;
		return;
	}
	dfs1(dep+1,sum+a[dep]);
	dfs1(dep+1,sum);
}
void dfs2(long long dep,long long sum){
	if(dep>nb){
		posb[++cntb]=sum;
		return;
	}
	dfs2(dep+1,sum+b[dep]);
	dfs2(dep+1,sum);
}
#undef int
int main(){
	
#define int long long
	read(n,m);
	na=(n+1)/2;nb=n/2;
	for(int i=1;i<=na;++i) read(a[i]);
	for(int i=1;i<=nb;++i) read(b[i]);
	dfs1(1,0);
	dfs2(1,0);
	//sort(posa+1,posa+cnta+1);
	sort(posb+1,posb+cntb+1);
	int ans=0;
	for(int i=1;i<=cnta;++i)
		ans+=(upper_bound(posb+1,posb+cntb+1,m-posa[i])-posb-1);
	cout<<ans<<endl;
    return 0;
}
//Welcome back,Chtholly.

洛谷P3067[USACO12OPEN]Balanced Cow Subsets G

这是一道折半搜索的进阶题,不是因为他涉及了更高层次的折半搜索,而是因为与其他知识牵连了起来。

题面还是很简单,设计折半思路也很简单。

显然,对于每一个数只有三种可能,放左边、放右边、都不放。所以如果爆搜时间复杂度是 \(\mathbf{O}(3^N)\) ,我们无法接受这个值。

折半思路就显然了,我实际只需要维护一个两边差是 \(0\) 的集合就 ok 了。所以维护一手差。自信 5min 写出代码。

#include <bits/stdc++.h>
#define debug puts("I ak IOI several times");
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template <typename T>inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
int n;
map<int,vector<int> >mp;
int a[55],ans;
bool book[1<<21];
void dfs(int dep,int d,int sta,int flag){
    if(dep>n/2&&flag){
        mp[d].push_back(sta);
        return;
    }
    if(dep>n&&!flag){
        for(int i=0;i<mp[-d].size();++i)
            book[sta|mp[-d][i]]=1;
        return;
    }
    dfs(dep+1,d+a[dep],sta|(1<<dep-1),flag);
    dfs(dep+1,d-a[dep],sta|(1<<dep-1),flag);
    dfs(dep+1,d,sta,flag);
}
int main(){
    read(n);
    for(int i=1;i<=n;++i) read(a[i]);
    dfs(1,0,0,1);
    dfs(n/2+1,0,0,0);
    for(int i=1;i<(1<<n);++i)
        ans+=book[i];
    cout<<ans<<endl;
    return 0;
}
//Welcome back,Chtholly.

TLE 91。

die码时间复杂度确实玄学,还用了 map 等去存储答案,估计是人傻常数大吧?然后开始了漫漫卡常路。

#include <bits/stdc++.h>
#define debug puts("I ak IOI several times");
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template <typename T>inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
int n,n1;
map<int,vector<int> >mp;
int a[55],ans;
bool book[1<<21];
void dfs1(int dep,int d,int sta){
    if(dep==n1+1){
        mp[d].push_back(sta);
        return;
    }
    dfs1(dep+1,d,sta);
    dfs1(dep+1,d+a[dep],sta|(1<<dep-1));
    dfs1(dep+1,d-a[dep],sta|(1<<dep-1));
}
void dfs2(int dep,int d,int sta){
    if(dep==n+1){
        if(!mp.count(d)) return;
        int i=0,k=mp[d].size();
        while(i<k){
            int tmp=mp[d][i],q=sta|tmp;
            ans+=(!book[q]);
            book[q]=1;
            ++i;
        }
        return;
    }
    dfs2(dep+1,d,sta);
    dfs2(dep+1,d+a[dep],sta|(1<<dep-1));
    dfs2(dep+1,d-a[dep],sta|(1<<dep-1));
}
int main(){
    read(n);
    for(int i=1;i<=n;++i) read(a[i]);
    n1=n/2;
    dfs1(1,0,0);
    dfs2(n1+1,0,0);
    write(ans-1);
    return 0;
}
//Welcome back,Chtholly.

TLE96 焯,下载数据看一眼,跑一跑电脑卡了。那肯定不能通过常数再来占便宜了,那么该怎么再优化呢?

只能翻看一手题解。 bitset ???

想起来好像 bitset 常数奇小无比。只得转战学习 bitset 。

然后对着题解码了码。

#include <bits/stdc++.h>
#define debug puts("I ak IOI several times");
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template <typename T>inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
int n,n1;
map<int,bitset<1<<11> >mp;
int a[55],ans;
bitset<1<<11>vis[1<<21];
bool book[1<<21];
void dfs1(int dep,int d,int sta){
    if(dep==n1+1){
        mp[d].set(sta);
        return;
    }
    dfs1(dep+1,d,sta);
    dfs1(dep+1,d+a[dep],sta|(1<<dep-1));
    dfs1(dep+1,d-a[dep],sta|(1<<dep-1));
}
void dfs2(int dep,int d,int sta){
    if(dep==n+1){
        if(mp.count(d)){
            bitset<1<<11>s(mp[d]);
            s&=~vis[sta];
            ans+=s.count();
            vis[sta]|=s;
        }
        return;
    }
    dfs2(dep+1,d,sta);
    dfs2(dep+1,d+a[dep],sta|(1<<dep-1));
    dfs2(dep+1,d-a[dep],sta|(1<<dep-1));
}
int main(){
    read(n);
    for(int i=1;i<=n;++i) read(a[i]);
    n1=n/2;
    dfs1(1,0,0);
    dfs2(n1+1,0,0);
    write(ans-1);
    return 0;
}
//Welcome back,Chtholly.
posted @ 2021-12-03 11:48  Mercury_City  阅读(224)  评论(0编辑  收藏  举报