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)。我们可以证明答案总是存在。
Exampleinput731016010111601100130003110300141100output
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;
}