CF935

C. Left and Right Houses

题目描述

在Letovo村庄里有n栋房子。村民们决定修建一条大街,将村庄分为左右两侧。每位居民都希望住在街道的左侧或右侧,这可以用序列a1,a2,…,an来描述,其中如果第j栋房子的居民想住在街道的左侧,则aj=0;否则,aj=1。

道路将穿过两栋房子。它左侧的房子将被宣布为左侧,右侧的房子将被宣布为右侧。更正式地,让道路穿过第i和第i+1栋房子。然后在位置1到i之间的房子将位于街道的左侧,在位置i+1到n之间的房子将位于右侧。道路也可能在第一栋房子之前或最后一栋房子之后通过;在这种情况下,整个村庄分别被宣布为右侧或左侧。

为了使设计公平,决定铺设道路,以便村庄每一侧至少有一半的居民对选择感到满意。也就是说,在一侧的x位居民中,至少应有⌈x/2⌉位希望住在该侧,其中⌈x⌉表示将实数x向上取整。

 

在道路的左侧,将有i栋房子,对应的aj中必须至少有⌈i/2⌉个零。在道路的右侧,将有n−i栋房子,对应的aj中必须至少有⌈n−i/2⌉个一。

确定道路应该铺设在哪栋房子之后,以满足所描述的条件,并尽可能靠近村庄的中间位置。形式上,对于所有合适的位置i,最小化∣∣n/2−i∣∣。

如果有多个最小∣∣n/2−i∣∣的合适位置i,则输出较小的一个。

输入 每个测试包含多个测试用例。第一行包含测试用例数量t(1≤t≤2⋅10^4)。接下来是测试用例的描述。

每个测试用例的第一行包含一个整数n(3≤n≤3⋅10^5)。每个测试用例的下一行包含一个长度为n的字符串a,只包含0和1。

保证所有测试用例中n的总和不超过3⋅10^5。

输出 对于每个测试用例,输出一个数字i—应该铺设道路的房子位置之后(如果应该在第一栋房子之前铺设道路,则输出0)。我们可以证明答案总是存在。

Example
input
7
3
101
6
010111
6
011001
3
000
3
110
3
001
4
1100

output

2
3
2
3
0
1
0

 解题思路

前缀和一下0和1,然后遍历一遍,取最小值,注意:如果存在多个解取最靠中间的,如果靠中间的程度相同优先取左边

#include<stdio.h>
#include<math.h>
char a[300010];
int dp[300010][2] = { 0 };
int main()
{
	int t, k;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d", &k);
		int sum = k, ans = 0;
		scanf("%s", a);
		for (int i = 1; i <= k; i++)//前缀和一下
		{
			dp[i][0] = dp[i - 1][0];
			dp[i][1] = dp[i - 1][1];
			if (a[i - 1] == '0')
				dp[i][0]++;
			else
				dp[i][1]++;
		}
		for (int i = 0; i <= k; i++)//遍历一遍找最小的情况
		{
			int g = (i + 1) / 2;
			int q = (k - i) / 2;
			if ((k - i) % 2 != 0)
				q++;
			if (dp[i][0] >= g && dp[k][1] - dp[i][1] >= q)
			{
				if (abs(k / 2 - i) < sum)
				{
					sum = abs((k + 1) / 2 - i);
					ans = i;
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

D. Seraphim the Owl

题目描述

有一群人排成一队,共有n个人,从第一个人i=1开始,向Serafim the Owl询问生命的意义。不幸的是,Kirill因为忙着为这个问题编写传奇故事,所以他稍晚到了一点,站在第n个人之后的队尾。Kirill对这种情况非常不满,所以他决定贿赂一些在他前面的人。

对于队列中的第i个人,Kirill知道两个值:ai和bi。如果此时Kirill站在位置i,那么他可以选择任何位置j,使得j<i,并与位置j的人交换位置。在这种情况下,Kirill必须支付给他aj个硬币。对于每个k,使得j<k<i,Kirill必须向位置k的人支付bk个硬币。Kirill可以执行任意次数的这种操作。

Kirill很节俭,所以他希望尽量少花硬币,但他又不想等太久,因此Kirill认为他应该是前m个人中的一员。

帮助Kirill确定他需要花费的最少硬币数量,以便不用等太久。

输入 

每个测试包含多组输入数据。第一行包含一个整数t(1≤t≤104)—测试用例的数量。然后是测试用例的描述。

每个测试用例的第一行包含两个整数n和m(1≤m≤n≤200000)—队列中除了Kirill之外的人数和Kirill允许的最大最终位置。

第二行包含n个整数a1,a2,…,an,用空格分隔(1≤ai≤109)。

第三行包含n个整数b1,b2,…,bn,用空格分隔(1≤bi≤109)。

保证所有测试用例中n值的总和不超过2⋅105。

输出

 对于每个测试用例,输出一个整数—Kirill需要花费的最少硬币数量。

 解题思路

本题采用贪心。假设我们站在位置i。找到第一个j,使得j<i且aj<bj。如果存在这样的j且j>m,则与j交换。这将是最优的,因为无论如何,我们都必须支付位置i−1,i−2,…,j的人一些硬币,通过这种方式,我们将为位置k的每个人支付一些硬币,其中i>k>j,bk硬币。根据贪婪条件bk>ak,因此bk是我们可以支付给第k个人的最少硬币数。我们还将支付第j个人aj硬币。aj<bj,因此我们将为所有人支付最少数量的硬币。

如果不存在这样的j,则对于我们来说,选择最终位置f是有利的,使得1≤f≤m,以便完成移动并尽量少地多付款。只需检查每个f,使用数组b上的前缀和重新计算答案,并选择最小的一个。

 

#include<stdio.h>
long long a[200010], b[200010], he[200010];
long long min(long long x, long long y)
{
	if (x > y)
		return y;
	else
		return x;
}
int main()
{
	long long t, n, m, sum;
	scanf("%lld", &t);
	while (t--)
	{
		scanf("%lld %lld", &n, &m);
		sum = 0; int u = n + 1;
		for (int i = 1; i <= n; i++)
			scanf("%lld", &a[i]);
		for (int i = 1; i <= n; i++)
			scanf("%lld", &b[i]);
		he[n + 1] = 0;
		for (int i = n; i >= 1; i--)//前缀和一下b[i]
			he[i] = he[i + 1] + b[i];
		for (int i = n; i >= m; i--)
		{
			if (a[i] <= b[i])
			{
				sum += a[i] + he[i + 1] - he[u];
				u = i;
			}
		}
		if (a[m] > b[m])//如果第m个的a大于b,则需要考虑插在m前面是否价格更低的情况
		{
			int i;
			long long hhh = 1e18;
			for (i = m; i >= 1; i--)
			{
				if (hhh > a[i] + he[i + 1] - he[u])
					hhh = a[i] + he[i + 1] - he[u];
				
			}
			sum += hhh;
		}
		printf("%lld\n", sum);
	}
	return 0;
}

  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

3分人生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值