搜索【折半搜索】【双向BFS】【dfs剪枝优化+A*/IDA*】

折半搜索

meet in mid

对于枚举指数级别大的,分成2个部分枚举,再考虑进行答案的拼接合并。从\(O(a^b)-->O(a^{b/2}*2)\)。拼接的方式可以是hash表查询,bitset标记,双指针单调性移动。

https://www.luogu.com.cn/problem/P5691】解方程,\(sigma(ki*xi^{pi})=0\),其中xi是需要你求出的不同解法,给出[1,m]的区间限制。n<=6.

正常枚举xi的每项\(O(150^6)\),考虑分成左区间和右区间分别求sum然后合并,就可以了。
积累实现方式:对于一个很大的val,我要记录出现次数,(map),hash表查询。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=300+10;
const int mod=5e6+7;
int P[153][9];
int n,m,k[9],p[9],mxp,table[mod+10],t[mod+10];
ll ans;
inline int Hash(int x)
{
    int v=(x>0)?(x%mod):((-x)%mod);
    while(t[v]&&table[v]!=x)
    {
        v++;if(v>=mod)v=0;
    }
    return v;
}
//int cnt=0;
inline void dfs1(int num,int val)
{
    if(num==(n>>1)+1)
    {
      //  ++cnt;
       // chu("%d %d:%d\n",(n>>1)+1,cnt,val);
        int pos=Hash(val);t[pos]++;table[pos]=val;
      //  chu("hash(%d):t[%d]:%d\n",val,pos,t[pos]);
        return;
    }
    _f(i,1,m)dfs1(num+1,val+P[i][p[num]]*k[num]);
}
inline void dfs2(int num,int val)
{
    if(num==n+1)
    {
        int pos=Hash(val);ans+=t[pos];return;
    }
    _f(i,1,m)dfs2(num+1,val-P[i][p[num]]*k[num]);
}
int main()
{
  
  // freopen("1.in","r",stdin);
  // freopen("1.out","w",stdout);
    n=re(),m=re();
    _f(i,1,n)k[i]=re(),p[i]=re(),mxp=max(mxp,p[i]);
    _f(i,1,m)
    _f(j,0,mxp)
    if(j==0)P[i][j]=1;
    else P[i][j]=P[i][j-1]*i;
    dfs1(1,0);
  // _f(i,0,5e6+6)
  // if(t[i])chu("t[%d]:%d\n",i,t[i]);
    dfs2((n>>1)+1,0);
    chu("%lld",ans);
    return 0;
}
/*
3
150
1 2
-1 2
1 2
*/

https://www.luogu.com.cn/problem/P3067】给出n个数,求把n个数取出若干个,若干个可以分成数值相同的2组的取法方案数。(n<=20)

对于一个数,有3种状态【1】选到A集合【2】选到B集合【3】不选,分别在累计sum的时候分+-号就行。问题变成把数分到3个集合让其中AB集合数sum相同的方案数,就是从左区间选出x个,右区间选出y个,让sum_x+sum_y=0的数集方案数,统计可以出现sum的个数然后求和就行。但是会出现重复,比如说同一个数集合,可能其中正负取不同都合法,但是只可以算一次-->(1)vis标记去掉重复(2)直接二进制数组记录所有状态,不累加方案而是去更新拼接的状态数,最后统计。
\(O(3^n*2)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=(1<<10)+100;
int a[22],n,mid;
ll ans;
bitset<N>vis[N],tmp;
map<int,bitset<N> >mp;
inline void ldfs(int now,int cost,int id)
{
	if(now==mid+1)
	{
		mp[cost].set(id,1);return;
	}
	ldfs(now+1,cost+a[now],id|(1<<(now-1)));
	ldfs(now+1,cost-a[now],id|(1<<(now-1)));
	ldfs(now+1,cost,id);
}
inline void rdfs(int now,int cost,int id)
{
	if(now==n+1)
	{
		if(mp[cost].count()==0)return;//没有
		tmp=mp[cost];//可以选择的数集合
		tmp&=(~vis[id]);
		ans+=tmp.count();
		vis[id]|=tmp;
		return;
	}
	rdfs(now+1,cost+a[now],id|(1<<(now-mid-1)));
	rdfs(now+1,cost-a[now],id|(1<<(now-mid-1)));
	rdfs(now+1,cost,id);
}
int main()
{
  // freopen("1.in","r",stdin);
  // freopen("1.out","w",stdout);
	n=re();mid=n>>1;
	_f(i,1,n)a[i]=re();
	ldfs(1,0,0);
	rdfs(mid+1,0,0);
	chu("%lld",ans-1);
    return 0;
}
/*
4 
1 
2 
3 
4 

*/

双向BFS

多起点互相奔赴,用size提前记录保证每个状态只更新了一轮。

https://blog.csdn.net/qq_29169749/article/details/51420097
在紫书上的搜索

*AandDFS

主要是剪枝和估价函数的计算。

posted on 2022-10-04 21:34  HZOI-曹蓉  阅读(41)  评论(0编辑  收藏  举报