NOIP2017提高组 模拟赛16(总结)

NOIP2017提高组 模拟赛16(总结)

第一题 比赛 (原题 noip2011 瑞士轮)

【题目描述】
【解题思路】

  每一次比赛过后,赢的N名选手是有序的,输的N名选手也是有序的(赢的+1,赢的那N个都+1,对位置不会有影响。输的也是一样)。直接用O(N)的时间归并即可。
  时间复杂度:O(N logN+RN)

【代码】
#include<cstdio>
#include<algorithm>

using namespace std;

typedef long long ll;

const int N=200100;
int n,t,ned;
int D[N],q1[N],q2[N],p1,p2;
struct data { int g,p,id; } d[N];

bool cmp(int A,int B) { return (d[A].g>d[B].g || (d[A].g==d[B].g && A<B)); }

int main()
{
    freopen("2241.in","r",stdin);
    freopen("2241.out","w",stdout);
    scanf("%d%d%d",&n,&t,&ned);
    n<<=1;
    for(int i=1;i<=n;i++) scanf("%d",&d[i].g);
    for(int i=1;i<=n;i++) scanf("%d",&d[i].p),D[i]=i;
    sort(D+1,D+1+n,cmp);
    while(t--)
    {
        p1=0; p2=0;
        for(int i=1;i<=n;i+=2)
        if(d[D[i]].p>d[D[i+1]].p)
        {
            d[D[i]].g++;
            q1[++p1]=D[i]; q2[++p2]=D[i+1];
        } else
        {
            d[D[i+1]].g++;
            q1[++p1]=D[i+1]; q2[++p2]=D[i];
        }
        p1=p2=1;
        for(int i=1;i<=n;i++)
        {
            if(p1<=(n>>1))
            {
                if(d[q1[p1]].g>d[q2[p2]].g || (d[q1[p1]].g==d[q2[p2]].g && q1[p1]<q2[p2]))
                D[i]=q1[p1++]; else D[i]=q2[p2++];
            } else D[i]=q2[p2++];
        }
    }
    printf("%d\n",D[ned]);
    return 0;
}

第二题 奶牛跑步

【题目描述】
【解题思路】

  比赛持续了T=C*L/speed_max
  奶牛i超过奶牛j的次数Pij=T*(speed_i-speed_j)/C=L/speed_max*(speed_i-speed_j),Pij要向下取整。
  由于L/speed_max是小数,不好搞。
  可以分开商和余数来做,先忽略掉余数(只考虑整数部分)
  例如3.2-1.5就变成3-1=2。
  那么这样是会算多的,因为0.2-0.5<0,必须答案-1。
  用树状数组存余数就可以解决这一问题。
  speed_max≤1000000,余数是存的下的。
  (速度从小到大排序)

【代码】
#include<cstdio>
#include<algorithm>

using namespace std;

typedef long long ll;

const int N=100100;
int n,len,sp[N];
ll T[1001000],ans,last,m,now;

void add(int x) { for(int i=x;i<=sp[n];i+=(i&-i)) T[i]++; }

ll query(int x)
{
    ll yu=0ll;
    for(int i=x;i;i-=(i&-i)) yu+=T[i];
    return yu;
}

bool cmp(int A,int B) { return (A<B); }

int main()
{
    freopen("2242.in","r",stdin);
    freopen("2242.out","w",stdout);
    scanf("%d%lld%d",&n,&m,&len);
    for(int i=1;i<=n;i++) scanf("%d",&sp[i]);
    sort(sp+1,sp+1+n,cmp);
    last=0ll; ans=0ll;
    for(int i=1;i<=n;i++)
    {
        ll q=m*sp[i]/sp[n];
        ll p=m*sp[i]%sp[n];
        last+=q;
        now=q*i-last;
        add(p+1);
        now-=(i-query(p+1));
        ans+=now;
    }
    printf("%lld\n",ans);
    return 0;
}

第三题 T-shirt (cf183d)

【题目描述】
【解题思路】

50%
  假设至少x人实际衬衫是k的概率是poss[k][x]
  假设第1到第y个人中至少x人实际衬衫是k的概率是f[k][y][x]
  f[k][y][x]=f[k][y-1][x-1] * pyk + f[k][y-1][x] * (1-pyk)
  Poss[k][x] = f[k][n][x]
  如果买x件衬衫,期望匹配数是sigma poss[k]i
  可以发现,k一定时,poss[k]单调减小。而假设原来有x件k号衬衫,现在多买一件k号衬衫,那么第x+1件k号衬衫对答案的贡献是poss[k][x+1]
  可以发现,答案是poss数组中,最大的n个数的和
  时间复杂度O(N*N*M)

100%
  利用单调性优化:如果poss[k][x]还未被选取,则必然不会选取poss[k][x+1].因此我们可以在选取了poss[k][x]后再计算poss[k][x+1]的值。
  时间复杂度O(N*N+N*M)

【代码】
#include<cstdio>
#include<algorithm>

#define imax(a,b) (((a-b)>eps)?(a):(b))

using namespace std;

typedef long long ll;

const double eps=1e-6;
const int N=3010;
const int M=310;
int n,m,g[M];
double fn[2][M][N],p[N][M],poss[M][2],ans;

void letplay(int cl)
{
    int yu=g[cl]&1^1;
    poss[cl][yu]=0.0;
    for(int i=1;i<=n;i++)
    fn[yu][cl][i]=fn[yu^1][cl][i-1]*p[i][cl]+fn[yu][cl][i-1]*(1.0-p[i][cl]);
    for(int i=0;i<=n;i++) fn[yu^1][cl][i]=0.0;
    poss[cl][yu]=fn[yu][cl][n];
    g[cl]^=1;
}

int main()
{
    freopen("2243.in","r",stdin);
    freopen("2243.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        int a; scanf("%d",&a);
        p[i][j]=1.0*a/1000;
    }
    for(int i=1;i<=m;i++)
    {
        g[i]=0;
        for(int j=0;j<=n;j++) fn[0][i][j]=1.0;
        letplay(i);
    }
    poss[0][0]=0.0; g[0]=0; ans=0.0;
    for(int i=1;i<=n;i++)
    {
        int mx=0;
        for(int j=1;j<=m;j++)
        if(poss[j][g[j]&1]>poss[mx][g[mx]&1]) mx=j;
        ans+=poss[mx][g[mx]&1];
        letplay(mx);
    }
    printf("%.8lf\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值