【学习笔记】折半搜索 Meet In The Middle

点击查看目录

题单
oi-wiki

算法实现

我们正常的搜索应该是一个指数级的:\(2^n\)

DFS

然而我们可以把这个搜索拆成两半,设小于整张图的限制 \(limit\) 为合法:

对于上半搜索,我们有若干符合限制的答案 \(sum_1\),对于下半搜索,我们有若干符合限制的答案 \(sum_2\)

因此对于下半搜索,有一个匹配的限制:\(limit-sum_2[i],i\in sum_2\),在这个限制下,有多个上半搜索的匹配,类似于:

时间复杂度:\(O(2^{\frac{n}{2}+1})\),可能会带上查找的 \(+logn\)

杂题乱写

[CEOI2015 Day2] 世界冰球锦标赛

题目链接

折叠题干

[CEOI2015 Day2] 世界冰球锦标赛

题目描述

译自 CEOI2015 Day2 T1「Ice Hockey World Championship

今年的世界冰球锦标赛在捷克举行。Bobek 已经抵达布拉格,他不是任何团队的粉丝,也没有时间观念。他只是单纯的想去看几场比赛。如果他有足够的钱,他会去看所有的比赛。不幸的是,他的财产十分有限,他决定把所有财产都用来买门票。

给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。

输入格式

第一行,两个正整数 \(N\)\(M(1 \leq N \leq 40,1 \leq M \leq 10^{18})\),表示比赛的个数和 Bobek 那家徒四壁的财产。

第二行,\(N\) 个以空格分隔的正整数,均不超过 \(10^{16}\),代表每场比赛门票的价格。

输出格式

输出一行,表示方案的个数。由于 \(N\) 十分大,注意:答案 \(\le 2^{40}\)

样例 #1

样例输入 #1

5 1000
100 1500 500 500 1000

样例输出 #1

8

提示

样例解释
八种方案分别是:

  • 一场都不看,溜了溜了
  • 价格 \(100\) 的比赛
  • 第一场价格 \(500\) 的比赛
  • 第二场价格 \(500\) 的比赛
  • 价格 \(100\) 的比赛和第一场价格 \(500\) 的比赛
  • 价格 \(100\) 的比赛和第二场价格 \(500\) 的比赛
  • 两场价格 \(500\) 的比赛
  • 价格 \(1000\) 的比赛

有十组数据,每通过一组数据你可以获得 10 分。各组数据的数据范围如下表所示:

数据组号 \(1-2\) \(3-4\) \(5-7\) \(8-10\)
\(N \leq\) \(10\) \(20\) \(40\) \(40\)
\(M \leq\) \(10^6\) \(10^{18}\) \(10^6\) \(10^{18}\)

这不板子题(

Miku's Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register int
#define MYMAX 20070831
typedef long double llf;
typedef long long ll;
typedef pair<int,int> PII;
const double eps=1e-8;
int Max(int x,int y)<%if(x<y)	return y;return x;%>
int Min(int x,int y)<%if(x<y)	return x;return y;%>
int Abs(int x)<%if(x<0)	return x*(-1);return x;%>
#if ONLINE_JUDGE
char in[1<<20],*p1=in,*p2=in;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#endif
inline ll read(){
	char c=getchar();
    ll x=0,f=1;
    while(c<48)<%if(c=='-')f=-1;c=getchar();%>
    while(c>47)x=(x*10)+(c^48),c=getchar();
    return x*f;
}const int maxn=45;

int n;
ll m,cost[maxn];
vector<ll> s1,s2;

void dfs(int st,int to,ll sum,vector<ll>&now){
	if(sum>m)	return;
	if(st>to){
		now.push_back(sum);
		return;
	}
	dfs(st+1,to,sum+cost[st],now);
	dfs(st+1,to,sum,now);
}

int main(){
	n=read(),m=read();
	for(rg i=1;i<=n;++i) cost[i]=read();
	dfs(1,n/2,0,s1);
	dfs(n/2+1,n,0,s2);
	sort(s1.begin(),s1.end());
	ll ans=0;
	int len2=s2.size()-1;
	for(rg i=0;i<=len2;++i)	ans+=upper_bound(s1.begin(),s1.end(),m-s2[i])-s1.begin();
	printf("%lld\n",ans);
}

2023年多校联训NOIP层测试2 T2 期末考试

题目链接

折叠题干

Problem B. 期末考试
Input file: standard input
Output file: standard output
Time limit: 1 second
Memory limit: 1024 megabytes

小X和小Y正在进行期末考试!
期末考试的试卷上共有10道选择题,每道题从ABCD中选择一个正确选项,选对得10分,选错得0分。 由于小Y上课时从来没有认真听讲,他只能每道题都从ABCD四个选项中等概率随机选择一个。正当 他焦头烂额之时,他看到了坐在他旁边的小X。聪明的小X自然会做每一道题目,但他不愿意直接告诉 小Y每道题的答案,而是让小Y把自己的答案传给小X,小X为小Y评分。 现在小Y已经将n 种答案交给了小X,小X也为其一一打分,但小Y还是不知道正确答案是什么,现在, 小Y想请你算算有多少种可能的正确答案?

Input
第一行输入一个T,表示数据组数。 接下来对于每组数据,首先输入一个n,表示小Y的答题次数。 接下来n 行,每行一个长度为10的字符串和一个数字a,表示小Y的答案和得分。

Output
输出T行,每行一个整数表示有多少种可能的正确答案。

Sample Input 1

3
3
AAAAAAAAAA 0
BBBBBBBBBB 0
CCCCCCCCCC 0
1
CCCCCCCCCC 90
2
AAAAAAAAAA 10
ABCDABCDAB 20

Sample Output 1

1
30
57456

Note
对于第一组数据,由于ABC都不可能是答案,答案为DDDDDDDDDD。 对于30%数据,T = 1, n ≤ 10。 对于另10%数据,a = 0。 对于另10%数据,a = 90。 对于100%数据,1 ≤ T ≤ 20000,1 ≤ n ≤ 20000,0 ≤ a ≤ 100,所有的a都是10的倍数。所有数据中 的n的和≤ 20000。

首先要知道这道题的暴力做法,纯搜索。

BF
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register int
#define MYMAX 20070831
typedef long double llf;
typedef long long ll;
typedef pair<int,int> PII;
const double eps=1e-8;
int Max(int x,int y)<%if(x<y)	return y;return x;%>
int Min(int x,int y)<%if(x<y)	return x;return y;%>
int Abs(int x)<%if(x<0)	return x*(-1);return x;%>
//#if ONLINE_JUDGE
//char in[1<<20],*p1=in,*p2=in;
//#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,1<<20,stdin),p1==p2)?EOF:*p1++)
//#endif
inline int read(){
	char c=getchar();
    int x=0,f=1;
    while(c<48)<%if(c=='-')f=-1;c=getchar();%>
    while(c>47)x=(x*10)+(c^48),c=getchar();
    return x*f;
}const int maxn=20050;

int T,n,ans,a[maxn];
char s[maxn][11],tryy[10];

void dfs(int pos,int n){
	int pts=0;
	if(pos==11){
		for(rg i=1;i<=n;++i){
			pts=0;
			for(rg j=1;j<=10;++j){
				if(tryy[j]==s[i][j])	pts+=10;
			}
			if(pts!=a[i])	return;
		}
		++ans;
	}
	else{
		for(rg i=1;i<=4;++i){
			tryy[pos]='A'-1+i;
			dfs(pos+1,n);
		}
	}
}

int main(){
	T=read();
	while(T--){
		ans=0;
		n=read();
		for(rg i=1;i<=n;++i){
			scanf("%s",s[i]+1);
			a[i]=read();
		}
		dfs(1,n);
		printf("%d\n",ans);
	}
	return 0;
} 

然后看到长度为 \(10\),考虑折半搜索,这是好的。

前五个字母在 \(n\) 个字符串的得分情况+后五个字符在 \(n\) 个字符串的得分情况=\(n\) 个字符串的得分情况。

\(n\) 个字符串的得分情况-后五个字符在 \(n\) 个字符串的得分情况=前五个字符在 \(n\) 个字符串的得分情况。

所以我们可以把等号前面的得分情况看作一个字符串,求哈希然后放容器里,等号后面的得分情况看作一个字符串,求哈希放桶里,最后查询一下等号前面的哈希在桶里出现了多少次即可。

Miku's Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register int
#define MYMAX 20070831
#define int long long
typedef long double llf;
typedef long long ll;
typedef pair<int,int> PII;
const double eps=1e-8;
int Max(int x,int y)<%if(x<y)	return y;return x;%>
int Min(int x,int y)<%if(x<y)	return x;return y;%>
int Abs(int x)<%if(x<0)	return x*(-1);return x;%>
//#if ONLINE_JUDGE
//char in[1<<20],*p1=in,*p2=in;
//#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,1<<20,stdin),p1==p2)?EOF:*p1++)
//#endif
inline int read(){
	char c=getchar();
    int x=0,f=1;
    while(c<48)<%if(c=='-')f=-1;c=getchar();%>
    while(c>47)x=(x*10)+(c^48),c=getchar();
    return x*f;
}const int maxn=20050,p=131,mod=19491001;

int T,n,ans,a[maxn];
char s[maxn][11],tryy[11];

vector<int> need;
map<int,int> bucket;

il void clear(){
	need.clear();
	bucket.clear();
	ans=0;
}

void dfs1(int st,int pos,int to,map<int,int>&now){
//将前五个变哈希 
	int pts=0;
	if(pos==to+1){
		int has=0;
		for(rg i=1;i<=n;++i){
			pts=0;
			for(rg j=st;j<=to;++j){
				if(tryy[j]==s[i][j])	pts+=10;
			}
			if(a[i]-pts<0 || a[i]-pts>50)	return;
			has=(has*p+pts)%mod;
		}
		++now[has];
	}
	else{
		for(rg i=1;i<=4;++i){
			tryy[pos]='A'-1+i;
			dfs1(st,pos+1,to,now);
		}
	}
}

void dfs2(int st,int pos,int to,vector<int>&now){
//针对后五个,得到需要的,变哈希 
	int pts=0;
	if(pos==to+1){
		int has=0;
		for(rg i=1;i<=n;++i){
			pts=0;
			for(rg j=st;j<=to;++j){
				if(tryy[j]==s[i][j])	pts+=10;
			}
			if(a[i]-pts<0 || a[i]-pts>50)	return;
//			for(rg j=st;j<=to;++j)	cout<<tryy[j];
//			cout<<endl;
			has=(has*p+a[i]-pts)%mod;
		}
		now.push_back(has);
	}
	else{
		for(rg i=1;i<=4;++i){
			tryy[pos]='A'-1+i;
			dfs2(st,pos+1,to,now);
		}
	}
}

signed main(){
	T=read();
	while(T--){
		ans=0;
		n=read();
		for(rg i=1;i<=n;++i){
			scanf("%s",s[i]+1);
			a[i]=read();
		}
		dfs1(1,1,5,bucket);
		dfs2(6,6,10,need);
		int len=need.size()-1;
		for(rg i=0;i<=len;++i)<% ans+=bucket[need[i]]; %>
		printf("%d\n",ans);
		clear();
	}
	return 0;
}

/*
1
10
DABBABBDDB 20
BABABDBBCB 30
CBCCDABCCC 20
CCDADDADAC 20
CBCDBAABCB 30
DADABBBCCB 20
ACBCADCBBD 30
DDDACDDBDA 20
DDBDBBDBCB 20
DCDCBDACCB 20
*/ 
posted @ 2023-09-10 16:40  Sonnety  阅读(35)  评论(1编辑  收藏  举报