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);
}