6296. 2019.08.12【NOIP提高组A】投票 三分 + dp + 概率

3 篇文章 0 订阅
2 篇文章 0 订阅

6296. 2019.08.12【NOIP提高组A】投票 
(File IO): input:vote.in output:vote.out

Time Limits: 1000 ms  Memory Limits: 524288 KB  Detailed Limits  

Description

Input

Output

Sample Input

Sample 1:
2 2
0.50 0.50 

Sample 2:
4 2
0.00 0.00 1.00 1.00 

Sample 3:
3 2
0.75 1.00 0.50 
 

Sample Output

Sample 1:
0.5

Sample 2:
1.0

Sample 3:
0.5
 

Data Constraint

Hint

Source / Author: kcz vote

 

题解:

首先 , 把一个人选的概率从小到大排序  , 答案一定是一段前缀和后缀。

可以理解为 , 若你跳过了一个人选了中间的话 , 答案不会更优。

因为要让平票的概率尽可能的大 , 假如一个人是好的 ,那么 我们希望这个人的p尽量大 , 不好那我们就希望这个人的p尽量小。

假若你选了中间的一个人。

若这个人选 , 那么右边还有p更大的你没选呢。

不选的话  , 那么左边还有p更小的你没选的呢。

这很显然, 因为如果左边/右边选了的话, 那又成了一段前缀 / 后缀。(此结论用数学归纳法应该可以更严谨地证明)。

 

 

SLU1

假设我们已经知道前缀部分要选出x个人 , 那么后缀部分也确定了(k-x)。

我们假设前缀里选出u个是投好的 , 那么后缀(k/2 - u)个选了好。

那我们只需要预处理dp一下 , 求出f[i][j]表示1~i中有j个好的概率 , g[i][j]表示j~n中有j个好的概率。

然后 , 在固定的x的意义下 ,  ans = sigma(f[x][u] * g[n-(k-x) +1][(k/2 - u)]); 枚举u 。

暴力枚举x和u就行了 , O(n^2(dp) + n^2(x,u));

SLU2

结合上面的 思想 , 我们其实可以有个优化。

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mcy(a,b) memcpy(a,b,sizeof(a))
#define N 2010
#define re register
#define open(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;


const double eps = 1e-8;
int in(int &x){
	char ch (0); x=0;
	while(ch<'0' || ch>'9') ch = getchar();
	while(ch>='0' && ch<='9') x = (x<<1) + (x<<3) + ch - '0' , ch = getchar();
	return x;
}
int n,k,haf=0;
double a[N],ans,f[N][N],g[N][N];

void dp()
{
	f[0][0] = 1.00;
	for(int i=1;i<=k;i++)
		for(int j=0;j<=i;j++) 
			f[i][j] = f[i-1][j] * (1.0 - a[i]) + (j ? (f[i-1][j-1] * a[i]) : 0.0);
}

void dp_()
{
	g[n+1][0] = 1.00;
	for(int i=n;i>=n-k+1;i--)
		for(int j=0;j<=(n-i+1);j++) 
			g[i][j] = g[i+1][j] * (1.0 - a[i]) + (j ? (g[i+1][j-1] * a[i]) : 0.0);
}



void work()
{
	for(int i=0;i<=k;i++)
	{
		double tmp=0;
		for(int u=0;u<=haf;u++) tmp = tmp + f[i][u] * g[n - (k-i) + 1][haf - u];
		ans = max(ans , tmp);
	}
}
int main()
{
	open("vote");
	in(n); in(k); haf = k/2;
	for(int i=1;i<=n;i++) scanf("%lf",&a[i]);		
	sort(a+1,a+1+n);
	dp();
	dp_();
	work();

	printf("%lf",ans);
}

设f(x)为当前缀长度x的答案。

发现f是一个单峰函数。

三分就行了。 博主写的三分模板 : https://blog.csdn.net/Com_man_der/article/details/99686395 

 

预计时间复杂度 O(n^2 + log n * n) ,跑出来差的不是很多。

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mcy(a,b) memcpy(a,b,sizeof(a))
#define N 2010
#define re register
#define open(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;


const double eps = 1e-8;
int in(int &x){
	char ch (0); x=0;
	while(ch<'0' || ch>'9') ch = getchar();
	while(ch>='0' && ch<='9') x = (x<<1) + (x<<3) + ch - '0' , ch = getchar();
	return x;
}
int n,k,haf=0;
double a[N],ans,f[N][N],g[N][N];

void dp()
{
	f[0][0] = 1.00;
	for(int i=1;i<=k;i++)
		for(int j=0;j<=i;j++) 
			f[i][j] = f[i-1][j] * (1.0 - a[i]) + (j ? (f[i-1][j-1] * a[i]) : 0.0);
}

void dp_()
{
	g[n+1][0] = 1.00;
	for(int i=n;i>=n-k+1;i--)
		for(int j=0;j<=(n-i+1);j++) 
			g[i][j] = g[i+1][j] * (1.0 - a[i]) + (j ? (g[i+1][j-1] * a[i]) : 0.0);
}

double calc(int x)
{
	int haf = k/2; 
	double ans=0;
		for(int u=0;u<=haf;u++) ans =ans + f[x][u] * g[(int)(n - (k-x) + 1)] [(int)(haf - u)] ;
	return ans;
}


void tri_search()
{
	int l=0 , r = k,m1(0),m2(0);
	while(l<=r)
	{
		m1 = l+r >> 1 , m2 = m1 + r >>1; 
		if(calc(m1)  > calc(m2)) 
		 r = m2-1;
		else   l = m1+1;
		ans = max(ans , max(calc(m1) , calc(m2)));
	}
}


int main()
{
	open("vote");
	in(n); in(k); haf = k/2;
	for(int i=1;i<=n;i++) scanf("%lf",&a[i]);		
	sort(a+1,a+1+n);
	dp();
	dp_();
	tri_search();
//	work();

	printf("%lf",ans);
}

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值