2024.3.11 训练记录(14)

继续补题

ICPC 2018青岛I Soldier Game

题目链接

线段树

果然稍微复杂一点的线段树就很难实现啊,不看题解根本没反应过来是线段树

struct Node
{
	int l, r, lb, rb, nb, b;
} tr[N * 4];

其中:

  • lb 表示 a[l] 不包含在区间之内,即 a[l] 包含在 [l - 1, l]
  • rb 表示 a[r] 不包含在区间之内,即 a[r] 包含在 [r, r + 1]
  • nb 表示 a[l]a[r] 都不包含在区间之内,即 a[l] 包含在 [l - 1, l] 中,a[r] 包含在 [r, r + 1]
  • b 表示 a[l]a[r] 都包含在区间之内,即 a[l] 包含在 [l, l][l, l + 1] 内, a[r] 包含在 [r, r][r - 1, r]

合并操作可以看这张图:
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;

#define int long long
using i64 = long long;

typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;

const int N = 1e5 + 10;
const int mod = 1e9 + 7;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;

int n;
int a[N];
struct Node
{
	int l, r, lb, rb, nb, b; // 分别表示左边界不包含 右边界不包含 两边界都不包含 两边界都包含
} tr[N * 4];

void pushup(Node& u, Node& l, Node& r)
{
	u.l = l.l, u.r = r.r;
	u.lb = min(max(l.lb, r.b), max(l.nb, r.lb));
	u.rb = min(max(l.b, r.rb), max(l.rb, r.nb));
	u.nb = min(max(l.lb, r.rb), max(l.nb, r.nb));
	u.b = min(max(l.b, r.b), max(l.rb, r.lb));
}

void pushup(int u)
{
	pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
	tr[u] = {l, r};
	if (l == r)
	{
		tr[u].lb = (l > 1 ? -INF : INF);
		tr[u].rb = (l == n ? INF : a[l] + a[l + 1]);
		tr[u].nb = INF;
		tr[u].b = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
	pushup(u);
	return;
}

void modify(int u, int pos, int len)
{
	if (tr[u].l == pos && tr[u].r == pos)
	{
		if (len == 1) tr[u].b = INF;
		else tr[u].rb = INF;
		return;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	if (pos <= mid) modify(u << 1, pos, len);
	else modify(u << 1 | 1, pos, len);
	pushup(u);
}

Node query(int u, int l, int r)
{
	if (tr[u].l >= l && tr[u].r <= r) return tr[u];
	int mid = tr[u].l + tr[u].r >> 1;
	if (r <= mid) return query(u << 1, l, r);
	else if (l > mid) return query(u << 1 | 1, l, r);
	else
	{
		Node res;
		auto left = query(u << 1, l, mid);
		auto right = query(u << 1 | 1, mid + 1, r);
		pushup(res, left, right);
		return res;
	}
}

void solve()
{
	cin >> n;
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	build(1, 1, n);
	vector<PIII> vec;
	for (int i = 1; i <= n; i ++ )
	{
		vec.push_back({a[i], {i, 1}});
		if (i != n) vec.push_back({a[i] + a[i + 1], {i, 2}});
	}
	sort(vec.begin(), vec.end());
	int ans = INF;
	for (int i = 0; i < 2 * n - 1; i ++ )
	{
		ans = min(ans, query(1, 1, n).b - vec[i].first);
		modify(1, vec[i].second.first, vec[i].second.second);
	}
	cout << ans << '\n';
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int t = 1;
	cin >> t;
	while (t -- )
	{
		solve();
	}
}

ICPC 2018青岛K Airdrop

题目链接

要说算法好像也没什么算法,原来金牌题也有纯思维吗,但是补得好困难啊

首先改变一下坐标轴,把 y = y0 作为 x 轴,因为所有人都是先上下再左右的,所以一定是先挪到 y 0 y_0 y0 这条线上再靠近 x 0 x_0 x0,同时处在 y 0 y_0 y0 两边的人是不可能相遇的,所以可以遍历 x 0 x_0 x0,判断处在左右两边的人对答案各贡献多少

下方以统计左侧为例说明统计方法

当然也是不能暴力统计的,我们注意到 x 0 x_0 x0 往右移的时候,左侧的人的曼哈顿距离都增加1,所以左侧原先贡献是多少,现在贡献还是多少

然后要看,上一次在 x = x 0 x=x_0 x=x0 上距离相等的点最多有 2 个,并且在 − d -d d d d d 的位置,这一次 x 轴右移,导致如果这两个点都存在,他俩就会撞死,同时还可能存在本来就在左侧的点和新出现在左侧的点曼哈顿距离一样,那他们就会一起撞死,只有这些情况的人数是1的时候才会对答案有1的贡献

我们只需要关注每个 x 和距离 x 为 1 的点

这题的另一个收获就是,开范围为 N 的数据结构时一定要开在最外面,开在里面会T

#include <bits/stdc++.h>

using namespace std;

#define int long long
using i64 = long long;

typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;

const int N = 1e5 + 10;
const int mod = 1e9 + 7;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;

unordered_map<int, int> mp[N];
vector<int> L(N), R(N);

void solve()
{
	int n, y0;
	cin >> n >> y0;
	vector<int> x(n + 1), y(n + 1);
	for (int i = 1; i <= n; i ++ )
	{
		cin >> x[i] >> y[i];
		y[i] -= y0;
	}

	vector<int> pos;
	for (int i = 1; i <= n; i ++ )
	{
		mp[x[i]][abs(y[i])] ++ ;
		for (int j = -1; j <= 1; j ++ ) pos.push_back(x[i] + j);
	}

	auto cal = [&](vector<int>& pos, vector<int>& f)
	{
		unordered_set<int> st;
		int dist = 0;

		f[pos[0]] = 0;
		for (int i = 1; i < pos.size(); i ++ )
		{
			int x_now = pos[i], lst = pos[i - 1];
			if (mp[lst].size() > 0)
			{
				for (auto t : mp[lst])
				{
					int yp = t.first, cnt = t.second;
					if (st.count(yp - dist) + cnt == 1) st.insert(yp - dist);
					else st.erase(yp - dist);
				}
			}
			dist += abs(x_now - lst);
			f[x_now] = st.size();
		}
		return;
	};

	sort(pos.begin(), pos.end());
	pos.erase(unique(pos.begin(), pos.end()), pos.end());
	cal(pos, L);
	reverse(pos.begin(), pos.end());
	cal(pos, R);

	int ans_max = 0, ans_min = INF;
	for (int i = 0; i < pos.size(); i ++ )
	{
		int xp = pos[i];
		int tmp = L[xp] + R[xp];
		if (mp[xp].size() > 0) for (auto t : mp[xp]) tmp += t.second;
		ans_max = max(ans_max, tmp);
		ans_min = min(ans_min, tmp);
	}
	cout << ans_min << ' ' << ans_max << '\n';

	for (int i = 0; i < pos.size(); i ++ ) mp[pos[i]].clear(), L[pos[i]] = R[pos[i]] = 0;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int t = 1;
	cin >> t;
	while (t -- )
	{
		solve();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Texcavator

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值