HDU 6070 区间问题

Dirt Ratio

Time Limit: 18000/9000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1026    Accepted Submission(s): 446
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 Xproblems 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.
 
题意:给出一个n,表示有n次提交,但提交过的题一定是过了的,但是对于判题系统来说,只有一次Accepted,而Accepted率 = 过的题数 / 总提交数 (同一道题只记一次AC)

现在要你找出一个区间,使他的 AC率最小,也就是说要找一个区间,使它的长度又长,所含不同的数字个数又少,这样才是AC率最小的情况

题目要求的精度只有 10^-4 ,但是题目给的数据非常大,6万的数据量,没法暴力

思路:对于找答案的题目,往往可以考虑二分找答案,这道题就可以二分,我们二分答案 为 mid 然后检验是否存在一个区间满足
\frac{size(l,r)}{r-l+1}\leq mid
rl+1size(l,r)​   mid  

可以把式子写成    size(l,r)+mid×lmid×(r+1)   

然后可以用线段树去维护 size(l r)

通过观察发现 二分出了 mid 之后,我们可以先把 mid * l 在创建线段树的时候就直接计算出来 ,然后后期枚举区间的右极限就可以计算出不等式左边部分了,而右边部分是很

容易得出的,然后每次去比较再取最小值就可以得出答案了,由于题目要求的精度是10^-4 所以只需要二分二十次就达到精度了,不需要太多

代码如下

#include<stdio.h>
#define ll long long
#define MAXN 60005

double min(double a,double b){
	return a < b ? a : b;
}

int a[MAXN],pre[MAXN],ql,qr;
double ans;

struct Tree{
	double minv[4 * MAXN],Mid;
	int addv[4 * MAXN];
	void maintain(int id,int l,int r){	
		if(r > l){		// 有区间存在 取儿子里值小的那个 
			minv[id] = min(minv[2 * id],minv[2 * id + 1]);
		}else{			// 没有的话 把它初始化为原来的 m * l 
			minv[id] = Mid * l;
		}
		if(addv[id])	// 答案就是要算 size(l,r) + m * l  
			minv[id] += addv[id];		// addv存的就是 size(l,r) 
	}
	void pushdown(int id){   	//把这个节点的统计赋值给儿子节点 
		addv[id * 2] += addv[id];	//懒惰数组,传递值给儿子 
		addv[id * 2 + 1] += addv[id];
		addv[id] = 0;
	}
	void update(int id,int l,int r){ 
		if(ql <= l && qr >= r){
			addv[id]++;   //如果当前的区间已经被目标区间所包含了,那么size(l,r)的值可以加一 
		}else{ 
			pushdown(id);	//否则把值传给儿子继续二分 
			int M = (l + r) / 2;
			if(ql <= M){ 	//如果目标区间左极限在 二分值左边,那么继续二分 
				update(id * 2,l,M);
			}else{			//如果不是,那么二分到这就可以了,去计算值 
				maintain(id * 2,l,M);
			}
			if(qr > M){		//这里同上 
				update(id * 2 + 1,M + 1,r); 
			}else{
				maintain(id * 2 + 1,M + 1,r);
			}
		}
		maintain(id,l,r);	// 防止没有计算到的部分 
	}
	void query(int id,int l,int r,int add){  
		if(ql <= l && qr >= r){		// 当前 l r 在 目标 ql qr之内的时候就去取小的答案 
			ans = min(ans,minv[id] + add);	//多增加的数 使 size(l,r) 增长 
		}else{		//否则去二分 
			int M = (l + r) / 2;
			if(ql <= M)				//二分的时候保留  size(l,r) 
				query(id * 2,l,M,add + addv[id]);	
			if(qr > M)
				query(id * 2 + 1,M + 1,r,add + addv[id]);
		}
	}
	void build(int id,int l,int r){
		if(l == r){		//到达叶子节点就计算 (m * l) 
			minv[id] = Mid * l;
			addv[id] = 0;	//初始化 记录 size(l,r) 的数组 
			return ;
		}
		int M = (l + r) / 2;
		build(id * 2,l,M);   
		build(id * 2 + 1,M + 1,r);
		minv[id] = min(minv[id * 2],minv[id * 2 + 1]);
		addv[id] = 0;
	}
}tree;

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		for(int i = 1;i <= n;i++){
			scanf("%d",&a[i]);
		}
		double l = 0,r = 1;
		for(int i = 0;i < 20;i++){
			double m = tree.Mid = (l + r) / 2.0; //二分枚举答案 
			tree.build(1,1,n); 		//建树  在建树同时就计算出 ( m * l )
			for(int j = 1;j <= n;j++)  //把 pre[]初始化为 0  
				pre[a[j]] = 0;			//这个数组的目的是使在一个区间内没个数的贡献都只为 1 
			int flag = 0;
			for(int x = 1;x <= n;x++){		//枚举右极限 
				ql = pre[a[x]] + 1;  	//标记下这个区间的左极限 和 右极限 
				qr = x;
				tree.update(1,1,n);		//处理 size(l,r) 
				pre[a[x]] = x;			//相同数字做记录 
				ans = 1e9;				//初始化为一个非常小的数 
				ql = 1;					//前面处理完了,去查找的时候要全部查找,所以赋值 1到 x 
				qr = x;
				tree.query(1,1,n,0);	//从 1到x去查找 
				if(ans <= m * (x + 1)){ 	//如果结果小于这个答案,那么答案还可以更小,继续二分 
					flag = 1;
					break;
				}
			}
			if(flag)
				r = m;
			else
				l = m;	
		}
		printf("%.10f\n",l);
	}
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值