2017多校训练赛第四场 HDU 6070(二分答案+线段树+扫描线)

Dirt Ratio

Time Limit: 18000/9000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1364    Accepted Submission(s): 622
Special Judge

Problem Description

In ACM/ICPC contest, the ''Dirt Ratio'' of a team is calculated in the following way. First let's ignore all the problems the team didn't pass, assume the team passed X problems during the contest, and submitted Y times for these problems, then the ''Dirt Ratio'' is measured as XY . If the ''Dirt Ratio'' of a team is too low, the team tends to cause more penalty, which is not a good performance.



Picture from MyICPC

Little Q is a coach, he is now staring at the submission list of a team. You can assume all the problems occurred in the list was solved by the team during the contest. Little Q calculated the team's low ''Dirt Ratio'', felt very angry. He wants to have a talk with them. To make the problem more serious, he wants to choose a continuous subsequence of the list, and then calculate the ''Dirt Ratio'' just based on that subsequence.

Please write a program to find such subsequence having the lowest ''Dirt Ratio''.

Input

The first line of the input contains an integer T(1T15) , denoting the number of test cases.
In each test case, there is an integer n(1n60000) in the first line, denoting the length of the submission list.
In the next line, there are n positive integers a1,a2,...,an(1ain) , denoting the problem ID of each submission.

Output

For each test case, print a single line containing a floating number, denoting the lowest ''Dirt Ratio''. The answer must be printed with an absolute error not greater than 104 .

Sample Input

 
 
1 5 1 2 1 2 3

Sample Output

 
 
0.5000000000
Hint
For every problem, you can assume its final submission is accepted.

Source

2017 Multi-University Training Contest - Team 4

 


        一道常规题,还是那句话,比赛的时候就是蠢……

        本题是问,区间内 (题目数目)/(区间长度)的最小值。不巧的是,这一类问题比较就没有遇到了,所以说比较蠢,没有想到最优比例生成树这个经典模型。最优比例生成树是今年寒假的时候就见到的模型,大致就是每条边有两个权值ai和bi,然后要你求一棵生成树,使得最后树的 所有ai之和/所以bi之和最大或者最小。解决的方法就是用二分答案,我们假设这个最后的比例是k,为了方便表示我们把ai之和表示为sa,bi之和表示为sb,那么有:sa/sb=k。然后我们设最优解为ans,有sa/sb=ans,移项有sa-ans*sb=0,又因为ans优于k(我们这里假设求比例最小),那么k>ans,即sa-k*sb<0。如此一来,机智的人就可以发现,sa-k*sb可以成为我们判定的式子,如果说我们二分枚举的k代入这个式子之后结果小于等于零,说明这个k是合法的,而且最优解小于等于它。反之,如果大于零,说明这个解不合法,不可能产生这么小的比例。

        然后这题也是类似的,根据题意我们可以得出以下式子:size(l,r)/(r-l+1)<=ans,其中size表示区间的不同的数字个数。即找到l、r使得比例最小,同样的进行变形得:size(l,r)+l*ans<=(r+1)*ans。我们二分枚举一个k,判断方法与上面说的类似。然后现在问题是,如何确定一个区间内不同的数字的个数。我们可以考虑用线段树+扫描线来解决这个问题。我们用每一个数字把区间分成很多个小段,然后从左到右依次扫描,扫描到某一个点之后,把以该点为右端点的线段加入线段树,即这条线段覆盖的区域的size值增加1。然后,我们线段树中每个点维护的就是那个size(l,r)+l*k,扫描到每一个i之后添加线段的同时,该点要加上l*k。之后,我们在看从1~i区间中的区间最小值,看是否已经小于等于(r+1)*k(这里相当于找一个左端点,然后对应的右端点是i),如果小于等于则当前k合法且最优解小于等于k,否则继续扫描直到扫描到n。如果到了n最小值还是不满足条件,说明当前k不合法。具体见代码:

#include<bits/stdc++.h>
#define eps 1e-4
#define N 60010
using namespace std;

int n,a[N],ls[N];

struct ST
{
	struct node
	{
		double min,lazy;
		int l,r;
	} tree[N<<2];

	inline void push_up(int i)
	{
		tree[i].min=min(tree[i<<1].min,tree[i<<1|1].min);
	}

	inline void build(int i,int l,int r)
	{
		tree[i].r=r;
		tree[i].l=l;
		tree[i].lazy=0;
		if (l==r)
		{
			tree[i].min=0;
			return;
		}
		int mid=(l+r)>>1;
		build(i<<1,l,mid);
		build(i<<1|1,mid+1,r);
		push_up(i);
	}

	inline void push_down(int i)
	{
		tree[i<<1].lazy+=tree[i].lazy;
		tree[i<<1|1].lazy+=tree[i].lazy;
		tree[i<<1].min+=tree[i].lazy;
		tree[i<<1|1].min+=tree[i].lazy;
		tree[i].lazy=0;
	}

	inline void update(int i,int l,int r,double x)
	{
		if ((tree[i].l==l)&&(tree[i].r==r))
		{
			tree[i].lazy+=x;
			tree[i].min+=x;
			return;
		}
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) update(i<<1,l,r,x);
		else if (mid<l) update(i<<1|1,l,r,x);
		else
		{
			update(i<<1,l,mid,x);
			update(i<<1|1,mid+1,r,x);
		}
		push_up(i);
	}

	inline double getmin(int i,int l,int r)
	{
		if ((tree[i].l==l)&&(tree[i].r==r)) return tree[i].min;
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) return getmin(i<<1,l,r);
		else if (mid<l) return getmin(i<<1|1,l,r);
		else return min(getmin(i<<1,l,mid),getmin(i<<1|1,mid+1,r));
	}

} seg;


int main()
{
    int T_T;
    cin>>T_T;
    while(T_T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        double l=0,r=1,mid;
        while(r-l>eps)
        {
            bool flag=0;
            mid=(l+r)/2.0;
            seg.build(1,1,n);					//其实没有必要每次都重新建树,是需要情况lazy和min即可,但是我懒得改模板了
            memset(ls,0,sizeof(ls));
            for(int i=1;i<=n;i++)
            {
                seg.update(1,ls[a[i]]+1,i,1);			//把以i为右端点的区间加1,表示size增加一个
                seg.update(1,i,i,i*mid); ls[a[i]]=i;		//把位置i加上i*mid,表示以i为左端点的贡献
                if (seg.getmin(1,1,i)<=(i+1)*mid){r=mid;flag=1;break;}		//判定解的合法性
            }
            if (!flag) l=mid;
        }
        printf("%.4f\n",r);
    }
    return 0;
}

       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值