1. 程式人生 > 其它 >CF EDU 105 C - 1D Sokoban

CF EDU 105 C - 1D Sokoban

C - 1D Sokoban

二分 + 找性質

可分正負的箱子分別討論

本題的關鍵是發現一個重要的性質:因為推箱子這個過程會讓被推到的箱子成為連續的一段,若想讓在特殊位置的箱子最多,則這一段的終點一定要在特殊位置上(起點也可以, 這裡的一定不是說不在特殊位置就取不到最優,而是在特殊位置上的某些情況一定可以取到最優,所以只考慮在特殊位置上的最大值就是全域性的最大值)

證明思路:若結尾不是在特殊位置上,那麼那把它移到最近的特殊位置上一定不會更劣

所以可以列舉每一個特殊位置,讓連續一段箱子的末尾被推到這個點上,看這時的答案是多少

答案 = 這一段箱子覆蓋的特殊位置 + 這一段箱子之後的點中,有多少箱子原來就在特殊位置上

第一項可二分求出,第二項可用字首和求出

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <map>
using namespace std;
typedef long long ll;

const int N = 2e5 + 10;
int n, m;
int a[N], b[N], c[N], d[N], pre[N];
map<int, bool> mp;

int solve(int a[], int b[], int n, int m)
{
	if (n <= 0 || m <= 0)
		return 0;
	sort(a + 1, a + n + 1);
	sort(b + 1, b + m + 1);
	
	mp.clear();
	for (int i = 1; i <= n; i++)
		mp[a[i]] = true;
	for (int i = 1; i <= m; i++)
	{
		pre[i] = pre[i-1];
		if (mp.count(b[i]))
			pre[i]++;
	}
	int ans = 0;
	for (int i = 1; i <= m; i++)
	{
		int len = upper_bound(a + 1, a + n + 1, b[i]) - a - 1;
		int r = b[i], l = r - len + 1;
		int cnt = upper_bound(b + 1, b + m + 1, r) - lower_bound(b + 1, b + m + 1, l);
		cnt += pre[m] - pre[i];
		ans = max(ans, cnt);
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n >> m;
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		for (int i = 1; i <= m; i++)
			cin >> b[i];
		int ans = 0;
		int cnta = 0, cntb = 0;
		//正數
		for (int i = 1; i <= n; i++)
			if (a[i] > 0) c[++cnta] = a[i];
		for (int i = 1; i <= m; i++)
			if (b[i] > 0) d[++cntb] = b[i];
		ans = solve(c, d, cnta, cntb);
		//負數
		cnta = 0, cntb = 0;
		for (int i = 1; i <= n; i++)
			if (a[i] < 0) c[++cnta] = -a[i];
		for (int i = 1; i <= m; i++)
			if (b[i] < 0) d[++cntb] = -b[i];
		ans += solve(c, d, cnta, cntb);
		cout << ans << endl;
	}
	return 0;
}