ABC393D题解

大概评级:黄。

签到题。

显然,我们需要找到所有的 \(1\) 的位置,然后看其它的 \(1\) 靠拢过来需要几次操作,然后对于每个位置的答案取个最小值就好了。

直接暴力肯定是不行的,考虑优化计算其它的 \(1\) 靠拢过来需要几次操作的过程,首先为了方便,我们将当前位置设为 \(i\),将 \(i\) 左边的 \(1\) 的位置组成的集合称为 \(a\),将 \(i\) 右边的 \(1\) 的位置组成的集合称为 \(b\),集合 \(a\) 的大小称为 \(as\),集合 \(b\) 的大小称为 \(bs\),那么集合 \(a\) 的贡献就是:

\[\sum_{j = 1}^{as} i-a_j-(as-j) \]

\[= \sum_{j = 1}^{as} a_j-as \times i-\sum_{j = 1}^{as-1} j \]

至于一开始的式子为什么还要减去 \(as-j\) 是因为每次 \(i\) 左边的一个 \(1\) 靠拢过来后,下一个 \(1\) 靠拢过来需要的次数就会少 \(1\)

继续正题,我们发现这个式子化简后是可以使用两个前缀和维护的。

额……其实集合 \(b\) 贡献也是差不多的:

\[\sum_{j = 1}^{bs} b_j-i-(bs-j) \]

\[= bs \times i-\sum_{j = 1}^{bs} b_j-\sum_{j = 1}^{bs-1} j \]

可以用两个后缀和维护。

十年 OI 一场空,不开 long long 见祖宗。

总时间复杂度:\(O(N+N) = O(N)\)

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 5e5+5;
char a[N];
int sum1[N];
int sum2[N];
signed main()
{
	int n;
	scanf("%lld",&n);
	scanf("%s",a+1);
	for(int i = n;i;i--)
	{
		sum1[i] = sum1[i+1]+(a[i] == '1');
		sum2[i] = sum2[i+1]+(a[i] == '1')*i;
	}
	int minn = 1e18;
	int pre1 = 0,pre2 = 0;
	for(int i = 1;i<=n;i++)
	{
		pre1+=(a[i] == '1');
		pre2+=(a[i] == '1')*i;
		if(a[i] == '1')
		{
			minn = min(minn,(pre1-1)*i-(pre2-i)-(pre1-1)-(pre1-1)*(pre1-2)/2+(sum2[i]-i)-(sum1[i]-1)*i-(sum1[i]-1)-(sum1[i]-1)*(sum1[i]-2)/2);
		}
	}
	printf("%lld",minn);
	return 0;
}
posted @ 2025-02-16 21:45  林晋堃  阅读(22)  评论(0)    收藏  举报