//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

Meet in the middle

我们都知道搜索是非常常用的一个东西,我们在解决不了问题的时候就会选择他来暴力寻找最优解。

一般的暴力的复杂度都是指数级,比如常见的 01 背包,复杂度就是 \(O(2^{n})\),而如果我们用了 meet in the middle,就可以让我们的时间复杂度降到 \(O(2^{\frac{n}{2}})\)

而他的思想也很简单,把问题从中间拆开,分成两部分来 DFS,一半是 \(1\sim \frac{n}{2}\),一半是 \(\frac{n}{2} + 1\sim n\)

然后在判断中间接起来的问题就可以了。

P2962 [USACO09NOV] Lights G

我们看到数据范围,只有 \(35\),我们考虑一下状压。

我们发现一个点只有两种状态,且一个改一个点会导致周围的点也都变,所以我们可以用一个数组,里面存放二进制数,每一位代表一个编号的点,1 表示白,0 表示黑。

我们在搜索的时候和上面一样分成两部分,我们用一个 map 来记录当前状态的最少步数,然后直接搜索即可。

#include <bits/stdc++.h>

#define INF INT_MAX
#define int long long
#define N 10100

using namespace std;

int mp[N], now, n, m, ans = INF;
map<int, int> a;

inline void dfs(int x, int u, int k, int t)//u是终点,k是当前状态,t是当前点的花费 
{
	if(a[k]) a[k] = min(a[k], t);
	else a[k] = t;
	if(a[k ^ now] || k == now)
		ans = min(a[k ^ now] + t, ans);
	if(x <= u)
	{
		dfs(x + 1, u, k, t);
		k ^= mp[x];
		dfs(x + 1, u, k, t + 1);
	}
}

signed main()
{
	cin >> n >> m;
	for(int i = 1; i <= m; i ++)
	{
		int u, v;
		cin >> u >> v;
		mp[u] ^= (1 << v);//处理每一个与之相连的点 
		mp[v] ^= (1 << u); 
	}
	for(int i = 1; i <= n; i ++)
	{
		mp[i] ^= (1 << i);//把每一个点都按位异或1 
		now ^= (1 << i);
	}
	dfs(1, n / 2, 0, 0);//折半搜索 
	dfs(n / 2 + 1, n, 0, 0);
	cout << ans << endl;
	return 0;
}

P5691 [NOI2001] 方程的解数

一看范围,有点大,一算,meet in the middle 能过。

我们分成两半后,不难发现一半和另一半的答案没有影响,我们直接用 map 来记录前半搜索的最后的 sum 值,然后第二半搜索的时候,直接加上 \(map[0 - sum]\) 就好了。

#include <bits/stdc++.h>

#define int long long
#define N 1000100

using namespace std;

int n, m, k[N], p[N], ans;
unordered_map<int, int> mp;

inline void dfs(int x, int u, int sum, int ll)
{
	if(x > u && ll == 1) return mp[sum] ++, void();
	if(x > u && ll == 2) return ans += mp[0 - sum], void();
	for(int i = 1; i <= m; i ++)
		dfs(x + 1, u, sum + k[x] * pow(i, p[x]), ll);
	return ;
}

signed main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++)
		cin >> k[i] >> p[i];
	int mid = n >> 1;
	dfs(1, mid, 0, 1);
	dfs(mid + 1, n, 0, 2);
	cout << ans << endl;
	return 0;
}

Maximum Subsequence

需要找模 \(m\) 后最大的值。

首先还是折半搜索出两部分的值,然后开始合并答案,我们有两种情况。

  1. $m\le a_{i} + b_{i} < 2m $,这个情况,我们就直接用两个数组中的最大值更新即可。

  2. \(a_{i} + b_{i} < m\),这个情况,我们就可以直接一个从头,一个从尾开始,找最大的不大于 \(m\) 的值,然后取 max。

#include <bits/stdc++.h>

#define int long long
#define M 1001000
#define N 40

using namespace std;

int n, P, mid, a[N], suma[M], cnta, sumb[M], cntb, ans, vis[N];

inline void dfs(int x, int u, int sum, int xx[], int &cnt)
{
	if(x > u) return xx[++ cnt] = sum, void();
	dfs(x + 1, u, sum, xx, cnt);
	dfs(x + 1, u, (sum + a[x]) % P, xx, cnt);
	return ;
}

signed main()
{
	cin >> n >> P;
	for(int i = 1; i <= n; i ++)
		cin >> a[i], a[i] %= P;
	mid = n >> 1;
	dfs(1, mid, 0, suma, cnta);
	dfs(mid + 1, n, 0, sumb, cntb);
	sort(suma + 1, suma + cnta + 1);
	sort(sumb + 1, sumb + cntb + 1);
	ans = max(ans, (suma[cnta] + sumb[cntb]) % P);
	int l = cnta, r = 1;
	while(r <= cntb)
	{
		while(suma[l] + sumb[r] >= P) l --;
		ans = max(ans, suma[l] + sumb[r]);
		r ++;
	}
	cout << ans << endl;
	return 0;
}

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

我们还是分成两部分来搜索,我们用两个数组存起来不同的两部分的方案。

搜完之后,我们在最后枚举其中一个,然后就开始二分查找另一个里面与当前加起来不超过限制的数量,然后累加即可。

#include <bits/stdc++.h>

#define int long long
#define M 10000100
#define N 60

using namespace std;

int n, m, w[N], suma[M], sumb[M], cnta, cntb, ans;

inline void dfs(int l, int r, int sum, int a[], int &cnt)
{
	if(sum > m) return ;
	if(l > r) return a[++ cnt] = sum, void();
	dfs(l + 1, r, sum + w[l], a, cnt);
	dfs(l + 1, r, sum, a, cnt);
	return ;
}

signed main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++) cin >> w[i];
	int mid = n >> 1;
	dfs(1, mid, 0, suma, cnta);
	dfs(mid + 1, n, 0, sumb, cntb);
	sort(suma + 1, suma + cnta + 1);
	for(int i = 1; i <= cntb; i ++)
		ans += upper_bound(suma + 1, suma + cnta + 1, m - sumb[i]) - suma - 1;
	cout << ans << endl;
	return 0;
}
posted @ 2023-07-05 10:00  北烛青澜  阅读(63)  评论(0编辑  收藏  举报