【题解】UVA1451 平均值 Average

一道很有意思的题目

题目大意:

给定一个由 0 和 1 组成的序列,求出一个子序列使其数字的平均值最大,并输出方案。

解题思路:

看到数据,立刻想到 \(O(n\log n)\) 的时间复杂度,
立马联想到二分

那么以什么为关键值二分呢?左端点?右端点?区间长度?

平均值。

没错,这道题可以用二分平均值的方法 AC 。通过设置左、右端点为 0 、1,二分中间值(假设为 \(mid\))并判断是否合法。那么问题就转换为:

给定一个有 0 和 1 组成的序列,求出一个子序列使其数字的平均值大于等于 \(mid\),并输出方案。

但是因为 \(mid\) 值不断变化,不方便求解,所以我们可以开一个临时数组,使里面的值都为 \(a_i-mid\) ,然后再求此序列中是否存在一个子序列使其数字的平均值大于等于 0 ,可以用前缀和 + 贪心解决。

其实没有这一步也可以实现

至此,幼儿园知识普及完毕问题明显已解决。

注意事项:

  • 一定要注意精度问题。
  • 输入用 string 更方便。
  • 二分中的 \(l\)\(r\)\(mid\) 是小数。
  • 要开 double !!!

码:

#include<bits/stdc++.h>
using namespace std;
typedef double LD;
const LD eps = 1e-6,inf = 0x3f3f3f3f;
const int N = 100002;
int t,n,m;
string str;
LD a[N],b[N],sum[N];
void input()
{
		scanf("%d%d",&n,&m);
		cin>>str;
		for(int i = 0;i < n;i++) a[i+1] = str[i]-'0';
		return;
}
bool check(LD x,bool f)
{
		sum[0] = 0;
		for(int i = 1;i <= n;i++) b[i] = a[i]-x;
		for(int i = 1;i <= n;i++) sum[i] = sum[i-1]+b[i];
		LD minn = inf*1.0,ans = -inf*1.0;
		int nowl = 1,ansl = 0,ansr = 0;
  		for (int i = m;i <= n;i++)
		{
			if (sum[i-m] <= minn) nowl = i-m+1;
			minn = min(minn,sum[i-m]);
			if(sum[i]-minn > ans+eps) ansl = nowl,ansr = i;
			ans = max(ans,sum[i]-minn);
		}
		if(f) printf("%d %d\n",ansl,ansr);
		return ans >= 0;
}
void getans()
{
		LD l = 0,r = 1,mid;
		while(l < r-eps)
		{
			mid = (l+r)/2;
			if(check(mid,0)) l = mid;
			else r = mid;
		}
		check(l+eps,1);
		return;
}
int main()
{
		scanf("%d",&t);
		while(t--)
		{
			input();
			getans();
		}
		return 0;
}
posted @ 2022-01-31 23:57  Daniel2020  阅读(61)  评论(0)    收藏  举报