【模拟赛】纪中提高A组 19.8.15 测试

Posted on 2019-08-22 21:41 opethrax 阅读(...) 评论(...) 编辑 收藏

Task.1 投票

题目大意:有 \(n\) 个同学投票,每个人有一个投赞成票的概率 \(p_i\)。现在要从中选出 \(k\) 个同学,使得这 \(k\) 个人投票的平票的概率最大。

数据范围:\(1\leq N,K\leq 2000\)

考场上自己生成数据找规律发现一定是对所有人的 \(p_i\) 排序后头尾各取几个人(可以不取),\(O(N^2)\) DP 拿到了 \(70\)。这个性质的正确性具体来说就是假设已经确定了一些人,再选一个人结果是关于选的这个人的 \(p\) 的一个一次函数(其他部分已经确定,为常数)。具体推导可以看:here。利用这条性质,我们只需要对前缀后缀各 \(O(N^2)\) 做一遍DP算出概率再 \(O(N)\) 枚举前面选几个人即可。复杂度 \(O(N^2)\)

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef double db;
const int N=2005;
int n,k;
db p[N],c[N],f[N][N],g[N][N],ans=0;

int main(){
    freopen("vote.in","r",stdin);
    freopen("vote.out","w",stdout);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%lf",&p[i]);
    sort(p+1,p+n+1);
    for(int i=n-k+1;i<=n;i++)c[n+1-i]=p[i];
    f[0][0]=g[0][0]=1.0;
    for(int i=1;i<=k;i++){
        for(int j=0;j<=i;j++){
            f[i][j]=f[i-1][j]*(1.0-p[i]);
            if(j>0)f[i][j]+=f[i-1][j-1]*p[i];
            g[i][j]=g[i-1][j]*(1.0-c[i]);
            if(j>0)g[i][j]+=g[i-1][j-1]*c[i];
        }
    }
    for(int i=0;i<=k;i++){
        db tmp=0;
        for(int j=0;j<=k/2;j++)tmp+=f[i][j]*g[k-i][k/2-j];
        ans=max(ans,tmp);
    }
    printf("%lf\n",ans);
    return 0;
}

Task.2 动态数点

题目大意:对于一个数列 \(a\),如果一段区间 \([l,r]\) 中存在 \(a_k\) 满足 \(\forall\ i\ a_k|a_i\),这段区间的价值就是 \(r-l\)。给出序列 \(a\),求出所以区间中最大的价值以及哪些区间价值最大。

数据范围:\(1\leq N\leq 5\cdot 10^5,1\leq a_i\leq 2^{32}-1\)

存在不止一个 \(O(N\log N)\) 的解法,比如预处理一个区间 \(gcd\) 的ST表,然后对于每一个位置考虑能不能作为 \(a_k\),向左向右拓展,比较区间 \(gcd\) 是否是它的倍数。

这里给出一种 \(O(N)\) 的解法:先从小到大枚举作为 \(a_k\) 向右边能拓展到的最远距离,这个时候这段距离中经过的点一定不会比当前的点更优,然后在倒着向左边做同样的事情,在这个过程中记录和更新答案即可,因为操作顺序的缘故,得到的答案也是有序的。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

template<class T>void read(T &x){
    x=0; char c=getchar();
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef long long ll;
const int N=500050;
int n;
ll a[N];
int left[N],right[N],len=-1,p[N],tot;
int main(){
    freopen("point.in","r",stdin);
    freopen("point.out","w",stdout);
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);int l,r; ll t;
    for(int k=1;k<=n;){
        r=k; t=a[k];
        while(r<n&&a[r+1]%t==0)++r;
        right[k]=r; k=r+1;
    }
    for(int k=n;k;k--)if(right[k]){
        l=k; t=a[k];
        while(l>1&&a[l-1]%t==0)--l;
        left[k]=l;
    }
    for(int i=1;i<=n;i++){
        if(right[i]-left[i]>len){
            len=right[i]-left[i];
            p[tot=1]=left[i];
        }
        else if(right[i]-left[i]==len)
            p[++tot]=left[i];
    }
    printf("%d %d\n",tot,len);
    for(int i=1;i<=tot;i++)printf("%d ",p[i]); puts("");
    return 0;
}

Task.3

题目大意:

数据范围:

正解 DP,非常有趣但是我没完全理解,坑待填。