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\) 后最大的值。
首先还是折半搜索出两部分的值,然后开始合并答案,我们有两种情况。
-
$m\le a_{i} + b_{i} < 2m $,这个情况,我们就直接用两个数组中的最大值更新即可。
-
\(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;
}
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/p/17527764.html
The heart is higher than the sky, and life is thinner than paper.