codeforces739e绝对的干货题解!!!!!

 

 

首先我们考虑所有期望由什么情况组成。

1.单独用a类型的球。2.单独用b类型的球。3 a类型和b类型一起用.

然后dp的话很容易想出一个o(n^3)的算法。。。f[n][a][b]前n个怪兽我们用a 个a球,b个b球得到的期望。太慢~~~~~~~虽然能过2333333333333.

之所以慢的原因是分量太多,那能不能合一起些呢?

比如(单独用a和a,b一起用算一类) (单独用b算一类)意思就是,a,b一起用的贡献算在a头上。

然后方程式 fa[n][a][b]不过还是(n^3)的。还是慢。。。那么能把b的单独用的贡献算在a头上吗,嗯,如果可以方程就是f[n][a],哈哈n^2了,但是有个问题,你哪能保证一定有那么多个b呢?前面我们n^3的时候单独开了位计数b的个数,现在木有了,b很阔能超限啊!!!,怎么办呢?重点来了,这个方法很妙啊,我们首先将b分成两份,一份是k,一份是b-k,这样减少,单独用b的方案产生的贡献,算在a头上的贡献,b-k是算在a头上的,这样导致了这个算在a头上的贡献是可调的,导致用b的数目也是可调的,试想如果k=0那么就是所有b的贡献都算在了a头上,这样固然很好(比如每个b都产生很大的贡献然后每次都用B球),但是木有那么多b球给你更新啊!!!!所以我们要调k的值啦,然后每固定一个k值你阔以把所有b球的值看成是b-k a球不变,然后每个b球的贡献都算在a头上,在这种情况下求出来的最大实际值是 f[n][a]+用的b球数*k。每个k值对应一个情况,k=0的时候就是原始情况,虽然这种情况来说,每个b木有减少,整体相对于其他情况一定是最好的,求出来的实际值也一定是最好的,但是我前面说过了,木有那么多b啊!!所以要二分k,k越小对应的情况越好,但是,b不一定有那么多,所以看着调吧。

 

//由于这个不是我写的所以我感觉代码有点丑。。。但是能解释清楚
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 2010;
int n, a, b;
double p[N], u[N], q[N];
double f[N][N]; int g[N][N];
#define eps 1e-9
#define nowf (f[i][j])
#define nowg (g[i][j])
int dcmp(double x)
{
	if (fabs(x)<eps)return 0;
	return ((x>0) ? 1 : -1);
}
void up(double &f1, int &g1, double f2, int g2)
{
	if (f2>f1 + eps){ f1 = f2; g1 = g2; }
}
int get(double K)
{
	for (int i = 1; i <= n; ++i)
		for (int j = 0; j <= a; ++j)
		{
		nowf = nowg = 0;
		up(nowf, nowg, f[i - 1][j], g[i - 1][j]);//这里什么球都没用
		up(nowf, nowg, f[i - 1][j] + u[i] - K, g[i - 1][j] + 1);//这里用的是b球因为(b-k)是从b球类分出来的所以用了(b-k)相当于用b
		if (j)up(nowf, nowg, f[i - 1][j - 1] + p[i], g[i - 1][j - 1]);//用a球
         if (j)up(nowf, nowg, f[i - 1][j - 1] + q[i] - K, g[i - 1][j - 1] + 1);//用a球和b球这里应该写成f+p[i]+(u[i]-k)+p[i]*u[i]就好理解
		}
	return g[n][a];
}
int main()
{
	scanf("%d%d%d", &n, &a, &b);
	for (int i = 1; i <= n; ++i)scanf("%lf", p + i);
	for (int i = 1; i <= n; ++i)scanf("%lf", u + i);
	for (int i = 1; i <= n; ++i)q[i] = p[i] + u[i] - p[i] * u[i];
	double l = -1e4, mid, r = 1e4;
	while (r - l>eps)
	{
		mid = (l + r)*0.5;//二分k值
		if (get(mid)<b)r = mid;
		else l = mid;
	}
	printf("%lf\n", f[n][a] + l*b);
	return 0;
}

嗯,上面的算法还是太慢啦23333.。。1000ms+

既然阔以分b那么为什么不能分a呢,嗯分a后就能达到nlogn*logn了。。。快的飞起!!!!30ms+

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
double u[3000], v[3000], p[3000];
double expect[3000];
int n, a, b;
int numa[3000], numb[3000];
void test(double aa, double bb)
{
	for (int i = 1; i <= n; i++)
	{
		double d1 = u[i] - aa;
		double d2 = v[i] - bb;
		double d3 = p[i] - aa - bb;
		double d4 = 0;
		numa[i] = numa[i - 1];
		numb[i] = numb[i - 1];
		expect[i] = expect[i - 1];
		if (d1 >= d2&&d1 >= d3&&d1 > d4)//比较顺序,和大于等于号是有讲究的。。
		{                                 //尽量少用d3,d1和d2在相等时随便选
			numa[i] += 1;
			expect[i] += d1;
			continue;
		}
		if (d2 >d1&&d2 >= d3&&d2 >d4)
		{
			numb[i]+= 1;
			expect[i]+= d2;
			continue;
		}
		if (d3 > d1&&d3 > d2&&d3 > d4)
		{
			numb[i] += 1;
			numa[i] += 1;
			expect[i] +=d3;
		}
		if (d4 >= d1&&d4 >= d3&&d4 >= d2)continue;
	}
}
int main()
{
	//double lllll = 0.000000000000000001;
	//cout << lllll <<endl;
	scanf("%d%d%d", &n, &a, &b);
	for (int i = 1; i <= n; i++)
	{
		scanf("%lf", &u[i]);
	}
	for (int i = 1; i <= n; i++)
		scanf("%lf", &v[i]);
	for (int i = 1; i <= n; i++)
		p[i] = u[i] + v[i] - u[i] * v[i];
	double ll = 0, rr = 1;
	double l, r;
	while (rr - ll > 1e-12)
	{
		double mida = (ll + rr) / 2;
		 l = 0, r = 1;
		while (r - l > 1e-12)
		{
			double midb = (l + r) / 2;
			test(mida, midb);
			if (numb[n] < b)
				r = midb;
			else
				l = midb;
			if (numb[n] == b)break;//同下
		}
	if (numa[n] < a)
		rr = mida;
	else
		ll = mida;
	if (numa[n] == a)break;//如果numa[n]==a的时候此时已经是极限了如果不break 然后ll=mida然后mida上升导致所得解偏小。
	}
//	cout << "ll:" << ll << " " << "l:" <<l << endl;
	//cout << ll*numa[n] + l*numb[n] << endl;
//	cout << numa[n] << " " << numb[n] << endl;
	//cout << expect[n] << endl;
	printf("%.5lf", expect[n] + numa[n]*ll + numb[n]*l);//注意这得写法不是 expect[n] + a*ll + b*l
}

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值