Meet in the middle

Meet in the middle

俗称折半搜索,将 m^n复杂度可降成 m^(n/2)

1.luoguP5691 [NOI2001] 方程的解数

朴素算法的复杂度为m^n m=150,n=6 稳稳地超时
but采用Meet in the middle 就可以将复杂度降成m^(n/2) 此时可过
考虑将式子拆成 1 ~ n/2 与 n/2+1 ~ n 项的两个部分A与B
方程为A+B=0,此时将其中一部分移项过去,A=-B
两边分别算,算完之后看B中的合法解是否有对应的相反数-B,出现在A中
累计和即可
方程的解可能很大,要用map或者手写Hash统计其对应的出现次数

//luoguP5691 [NOI2001] 方程的解数
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;

typedef long long ll;
const ll N=151;

inline ll read() {

	ll x=0,f=1;
	char ch=getchar();
	while(ch<48||ch>57) {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>=48&&ch<=57) {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;

}

inline ll ksm(ll a,ll b) {

	ll ans=1,p=a;
	while(b) {

		if(b&1) ans*=p;
		p*=p;
		b>>=1;

	}
	return ans;

}

ll n,m,cnt=0;
ll k[N],p[N];
tr1::unordered_map<ll,ll> mp;
tr1::unordered_map<ll,ll>::iterator it;

inline void dfs(ll dep,ll x) {

	for(register ll i=1;i<=m;i++) {

		ll tmp=x+(k[dep]*ksm(i,p[dep]));
		if(dep==(n/2)) {

			mp[tmp]++;
			continue;

		} else if(dep==n) {

			it=mp.find(-tmp);
			if(it!=mp.end()) cnt+=it->second;
			continue;

		}
		dfs(dep+1,tmp);

	}
	return ;

}

int main() {

	n=read();
	m=read();
	for(register ll i=1; i<=n; i++) {

		k[i]=read();
		p[i]=read();

	}

	dfs(1,0);
	dfs(n/2+1,0);
	printf("%lld\n",cnt);

	return 0;
}

2.好题CF888E Maximum Subsequence

首先不妨处理出可以合并出来的取模结果
分成前后两段,就是一个朴素的Meet in the middle
两部分之和显然小于2m,
对于和,可以进行一个非常妙的分讨
1.[0,m)中,前后排完序,对于前半部分l,选择最大的(即后半部分r与其相加最大的那个,和<=m)加起来就好了!!!
2.[m,2m)中,直接取最大值即可,一步比较即可结束!!!
可以用双指针实现1,决策的单调性显然

//CF888E 印象深刻的题号 
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

inline ll read() {

	ll x=0,f=1;
	char ch=getchar();
	while(ch<48||ch>57) {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>=48&&ch<=57) {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;

}

ll n,m;
ll a[51];
ll ans=0;

vector<ll> p[2];

void dfs(ll op,ll x,ll sum,ll lim) {

	sum%=m;
	if(x>lim) {
		p[op].push_back(sum);
		return ;
	}
	dfs(op,x+1,sum+a[x],lim);
	dfs(op,x+1,sum,lim);

}

int main() {

	n=read();
	m=read();
	for(ll i=1; i<=n; i++) {

		a[i]=read();
		a[i]%=m;

	}

	dfs(0,1,0,(n>>1));
	dfs(1,(n>>1)+1,0,n);

	sort(p[0].begin(),p[0].end());
	sort(p[1].begin(),p[1].end());
	ll l=0,r=p[1].size()-1;
	ans=max(ans,(p[0][l]+p[1][r])%m);
	while(l<p[0].size()&&r>=0) {

		while(p[0][l]+p[1][r]>=m) r--;
		ans=max(ans,(p[0][l]+p[1][r])%m);
		l++;

	}

	printf("%lld\n",ans);

	return 0;
}

3.luoguP4799 [CEOI2015 Day2] 世界冰球锦标赛

这一题和CF888E真的很像,折半搜索照搬
决策的单调性更加显然
考虑前后半部分可得值排序后,与前半部分的l不满足的后半部分
对于l+1...等也都不满足
同时由于序列有序,可以满足l的只有后半部分1~r
还要考虑自己本身也可以不选后半部分这一方案
双指针修改区间范围后,ans+=r+1即可

//P4799 [CEOI2015 Day2] 世界冰球锦标赛
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll N=51;

ll read() {
	
	ll x=0,f=1;
	char ch=getchar();
	while(ch<48||ch>57) {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>=48&&ch<=57) {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;
	
}

ll n,m,ans=0,a[N];
vector<ll> p[2];

void dfs(ll op,ll x,ll sum,ll lim) {
	
	if(sum>m) return ;
	if(x>lim) {
		p[op].push_back(sum);
		return ;
	}
	dfs(op,x+1,sum+a[x],lim);
	dfs(op,x+1,sum,lim);
	
}

int main() {
	
	n=read();
	m=read();
	
	for(ll i=1;i<=n;i++) {
		
		a[i]=read();
		
	}
	
	dfs(0,1,0,n/2);
	dfs(1,n/2+1,0,n);
	
	sort(p[0].begin(),p[0].end());
	sort(p[1].begin(),p[1].end());
	
	ll l=0,r=p[1].size()-1;
	while(l<p[0].size()&&r>=0) {
		
		while(p[0][l]+p[1][r]>m) r--;
		ans+=r+1;
		l++;
		
	}
	
	printf("%lld\n",ans);
	
	return 0;
} 

4.luoguP3067 [USACO12OPEN]Balanced Cow Subsets G

刚拿到这题的时候,思维被“折半”限制住了,认为只能分两组
看了题解之后,发现每一头奶牛都有放在a组,放在b组,不放任何一组三种情况
然后可以将奶牛分成两半,分别计算三种情况
时间复杂度O(3^(n/2))

假设在前一半中,在第一组中放的数的和为a,在第二组中放的数为b。
假设在后一半中,在第一组中放的数的和为c,在第二组中放的数为d。
那么a+c=b+d
由于我们要对每一半分开处理,所以考虑将同一半的数放在一起处理,即移项得a-b=c-d
这个分组和移项非常的妙,只在其中一半区间的数也可以被包含了进去

//P3067 [USACO12OPEN]Balanced Cow Subsets G
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll N=2e6+10;

ll read() {

	ll x=0,f=1;
	char ch=getchar();
	while(ch<48||ch>57) {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>=48&&ch<=57) {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;

}

ll n,m,ans=0,a[N],tot=0,v[N];
vector<ll> p[N];
map<ll,ll> b;

void dfs1(ll x,ll sum,ll now,ll lim) {

	if(x>lim) {
		
		if(!b[sum]) b[sum]=++tot;
		p[b[sum]].push_back(now);
		return ;
		
	}
	dfs1(x+1,sum+a[x],now|(1<<(x-1)),lim);
	dfs1(x+1,sum-a[x],now|(1<<(x-1)),lim);
	dfs1(x+1,sum,now,lim);

}

void dfs2(ll x,ll sum,ll now,ll lim) {

	if(x>lim) {
		
		ll u=b[sum];
		for(ll i=0;i<p[u].size();i++) v[p[u][i]|now]=1;
		return ;
		
	}
	dfs2(x+1,sum+a[x],now|(1<<(x-1)),lim);
	dfs2(x+1,sum-a[x],now|(1<<(x-1)),lim);
	dfs2(x+1,sum,now,lim);

}

int main() {

	n=read();
	for(ll i=1; i<=n; i++) {

		a[i]=read();

	}

	dfs1(1,0,0,n/2);
	dfs2(n/2+1,0,0,n);
	
	for(ll i=1;i<=(1<<n);i++) ans+=v[i];
	printf("%lld\n",ans);

	return 0;
}

撒花

posted @   Diamondan  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示