ABC330 A-F

TOYOTA SYSTEMS Programming Contest 2023(AtCoder Beginner Contest 330) - AtCoder

老了手速跟不上了...写的巨墨迹的一场

A - Counting Passes

题意:

N人参与考试,考试分数不低于L分及格,给出每个人的分数Ai,问多少人及格

题解:

void solve()
{
	int n, l, ans = 0;
	scanf("%d%d", &n, &l);
	for (int i = 1, x; i <= n; ++i)
	{
		scanf("%d", &x);
		if (x >= l)++ans;
	}
	printf("%d\n", ans);
}

B - Minimize Abs 1

题意:

给出N个数和一对L,R,对于每个Ai求对于所有Y(L<=Y<=R),求一个Xi(L<=Y<=R)使得\left | Xi-Ai \right |\leq \left | Y-Ai \right |

题解:

显然Xi是在L,R范围内距离Ai最近的点

void solve()
{
	int n, l, r;
	scanf("%d%d%d", &n, &l, &r);
	for (int i = 1, x; i <= n; ++i)
	{
		scanf("%d", &x);
		if (x <= l)printf("%d ", l);
		else if (x <= r)printf("%d ", x);
		else printf("%d ", r);
	}
}

C - Minimize Abs 2

题意:

给出D,求最小的\left | x^{2}+y^{2}-D \right |

题解:

枚举x直到第一个x*x>=D,再往下枚举必然不优,故x枚举到\left \lfloor sqrt(D) \right \rfloor+1即可

接下来二分使得x*x+y*y<=D最大的y,此时y取y与y+1都可能是最优的,答案对所有情况取min即可

void solve()
{
	LL d, ans = 1e12;
	scanf("%lld", &d);
	int m = sqrt(d);
	for (LL x = 0; x <= m + 2; ++x)
	{
		LL l = 0, r = m;
		while (l < r)
		{
			LL mid = l + r + 1>> 1;
			if (x * x + mid * mid <= d)
				l = mid;
			else
				r = mid - 1;
		}
		LL s = x * x + l * l;
		ans = min(ans, abs(s - d)), ++l;
		s = x * x + l * l;
		ans = min(ans, abs(s - d));
	}
	printf("%lld\n", ans);
}

D - Counting Ls

题意:

给出一张N*N的仅由'o'与'x'构成的表,求出符合以下条件的点三元组的数量

1、三点互不重合

2、三点都是'o'

3、至少有两点在同一行

4、至少有两点在同一列

题解:

三点必然符合(x, y),(x, y'),(x', y),我们可以枚举点(x, y),而在x行上我们可以选择任意一个列数不是y的'o',在y列上我们可以选择任意一个行数不是x的'o'

设r[x]为x行上'o'的数量,c[y]为y列上'o'的数量,则对于枚举到点(x, y)时,另外两点的选择数量为(r[x] - 1) * (c[y] - 1),枚举x, y计数即可

char ch[N][N];
int r[N], c[N];
void solve()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
	{
		getchar();
		for (int j = 1; j <= n; ++j)
		{
			ch[i][j] = getchar();
			if (ch[i][j] == 'o')
				++r[i], ++c[j];
		}
	}
	LL ans = 0;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= n; ++j)
		{
			if (ch[i][j] == 'o')
				ans += (r[i] - 1) * (c[j] - 1);
		}
	}
	printf("%lld\n", ans);
}

E - Mex and Update

题意:

对于一个长度为N的数组A,给出q次单点修改操作,每次操作后输出该数组的mex

题解:

挺典的?维护一个数组的mex,做法很多,这里仅介绍一种相对简单的

首先对于值大于等于N的数完全不用管,因为显然N个数的mex必然不会大于N

我们对所有小于N的数维护一个桶cnt,同时用set维护所有cnt[x] == 0的x,即集合中不存在的数

当cnt[x]减少至0时,我们往set里加入x;当cnt[x]从0增加至1时,我们从set里删除x。

当然在这之前我们需要把所有0到N的数加入集合

每次修改后输出*st.begin()即可

int n, q, a[N], cnt[N];
set<int>st;
void add(int x)
{
	if (x > n)return;
	if (++cnt[x] == 1)
		st.erase(x);
}
void del(int x)
{
	if (x > n)return;
	if (--cnt[x] == 0)
		st.insert(x);
}
void solve()
{
	scanf("%d%d", &n, &q);
	for (int i = 0; i <= n; ++i)
		st.insert(i);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d", &a[i]);
		add(a[i]);
	}
	while (q--)
	{
		int idx, x;
		scanf("%d%d", &idx, &x);
		del(a[idx]);
		add(x);
		a[idx] = x;
		printf("%d\n", *st.begin());
	}
}

F - Minimize Bounding Square

题意:

给出二维平面中的N个点(Xi, Yi),我们可以对这些点进行K次操作,每次操作可以选择一个点然后使得该点往周围四格移动一格,在经过不多于K次操作之后,求一个最小的边长D,使得所有点都能被框在一个边长为D的正方形中(包括边界)

题解:

二分答案+双指针check。基本一眼出思路但是写的巨慢的一集

首先,当小的D满足条件,则大的D一定也满足条件,答案D满足二分性,因此我们可以二分答案D

而对于每个边长D的check只需要判断将所有的点移动到一个边长为D的正方形中的最小操作步数step<=k即可

再首先,当我们确定了最终的正方形的位置及大小,显然我们是可以分开计算x方向需要的步数和y方向需要的步数的,因此对于x数组和y数组我们完全可以分开考虑

那么此时check就可以变成判断将所有xi移动到一个长为D的区间内的操作次数stepx与将所有yi移动到一个长为D的区间内的操作次数stepy之和 stepx+stepy<=k即可

这个问题有点像把货仓选址问题的选择一个点换成了选择一个区间,最小的距离和即本题的最小操作数

首先对于这个长确定为D的区间的位置选择上,我们可以证明一定是存在点在边界上的情况时这个选择是最优的(或者与别的情况等同最优):当区间边界上不存在点时,我们将边界左移或者右移一格在区间内部的点距离区间的距离不会改变(都是0),设此时区间左边有sl个点,右边有sr个点,若sl<sr,我们将区间右移一格能够使得操作步数减少sr-sl步;若sl=sr移动了也不会有什么损失;若sl>sr,左移,步数减少sl-sr

对于左边界上有点的情况,我们只需要对数组a排序之后,从左往右枚举xi作为左边界,同时维护最右边的在区间内的点下标r与此时需要的操作步数s,初始化时我们默认左边界为0,从左往右枚举每个点若不大于d则更新r,若大于则 s += a[i] - d。当每次我们将左边界从i-1转移至i时,设此时区间移动的距离为dt = a[i] - a[i - 1],所有在点i左侧的点(共i - 1个)距离区间的距离都增加了dt,所有右侧的点(共n - r个)距离区间的距离都先减少了dt,然后我们更新r值,对于所有经过此次移动进入区间内部的点我们还需要加回去之前额外减少的距离a[i] + d - a[t] (t为所有新加入区间的点的下标)

而对于所有右边界上有点的情况,我们只需要将所有tx[i] = 1e9 - x[i],将tx数组排序并对tx数组进行一次上述操作即可

对两种情况取min即对于x(或y)数组在区间长度为d的情况下的最少操作次数stepx(或stepy)

emmmm讲的好复杂,直接看代码吧(

LL n, k, x[N], y[N], tx[N], ty[N];
LL fun(LL a[], LL d)
{
	LL mn = k + 1, s = 0, r = 0;
	for (int i = 1; i <= n; ++i)
	{
		if (a[i] > d)
			s += a[i] - d;
		else
			r = i;
	}
	for (int i = 1; i <= n; ++i)
	{
		s += (LL)(i - 1) * (a[i] - a[i - 1]);
		s -= (LL)(n - r) * (a[i] - a[i - 1]);
		while (r < n && a[r + 1] <= a[i] + d)
			s += a[i] + d - a[++r];
		mn = min(mn, s);
	}
	return mn;
}
bool check(LL d)
{
	return min(fun(x, d), fun(tx, d)) + min(fun(y, d), fun(ty, d)) <= k;
}
void solve()
{
	int mx = 1e9 + 10;
	scanf("%lld%lld", &n, &k);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%lld%lld", &x[i], &y[i]);
		tx[i] = mx - x[i], ty[i] = mx - y[i];
	}
	sort(x + 1, x + 1 + n);
	sort(y + 1, y + 1 + n);
	sort(tx + 1, tx + 1 + n);
	sort(ty + 1, ty + 1 + n);
	int l = 0, r = mx;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (check(mid))
			r = mid;
		else
			l = mid + 1;
	}
	printf("%d\n", l);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值