(思维+二分+dp)C. 1D Sokoban
思路
\(我们去枚举每一个b[i]为k,将其作为向右推箱子走到的最远的下标,那么答案就有左右两部分:\)
- \(右边:最远动过的箱子到k,那么对于初始位置\ge k且处于特殊位置的箱子要算入答案.f[i]表示从i到n箱子已经在特殊位置的个数.\)
- \(左边: 对于每一个k,先统计\le k的箱子有多少个,此处用到第一次二分.\\左边只有num个箱子,所以对位置k,最左只用考虑到k-num这个位置\\于是求出出现\ge k-num的第一个数的下标left,此处用到第二次二分.\)
- \(ans=max(ans,i-left+1+f[i+1])\)
- \(由于可以不推,所以ans至少是f[0].\)
- \(左右两边都是独立的,把左边放到右边来做.\)
- \(upper\_bound()返回第一个\gt x的数.lower\_bound()返回第一个\ge x的数.\)
代码
#include <bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
inline int lowbit(int x) { return x & (-x); }
#define ll long long
#define ull unsigned long long
#define pb push_back
#define PII pair<int, int>
#define VIT vector<int>
#define x first
#define y second
#define inf 0x3f3f3f3f
const int N = 2e5 + 7;
int f[N];
int cal(VIT a, VIT b) {
memset(f, 0, sizeof f);
unordered_map<int, int> mp;
int n = a.size(), m = b.size();
for (int i = 0; i < n; ++i) mp[a[i]] = 1;
for (int i = m - 1; i >= 0; --i) f[i] = f[i + 1] + mp[b[i]];
int ans = f[0];
for (int i = 0; i < m; ++i) {
int num = upper_bound(a.begin(), a.end(), b[i]) - a.begin();
int left = lower_bound(b.begin(), b.end(), b[i] - num + 1) - b.begin();
ans = max(ans, i - left + 1 + f[i + 1]);
}
return ans;
}
int main() {
//freopen("in.txt", "r", stdin);
IO;
int _;
cin >> _;
while (_--) {
int n, m;
cin >> n >> m;
VIT a_pos, a_neg, b_pos, b_neg;
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
if (x > 0) a_pos.pb(x);
else a_neg.pb(-x);
}
for (int i = 0; i < m; ++i) {
int x;
cin >> x;
if (x > 0) b_pos.pb(x);
else b_neg.pb(-x);
}
reverse(a_neg.begin(), a_neg.end());
reverse(b_neg.begin(), b_neg.end());
int ans = cal(a_pos, b_pos) + cal(a_neg, b_neg);
cout << ans << '\n';
}
return 0;
}