BZOJ4391: [Usaco2015 dec]High Card Low Card

17 篇文章 0 订阅
10 篇文章 0 订阅

题目大意:给定对方的出牌序列以及你的手牌,双方手牌一共是一个1到2n的全排列,一开始是点大胜出,你可以在任意时刻改一次规则变成点小胜出,求最多能赢多少次


用贪心的思想,肯定一开始尽量用点大的,改规则之后用点小的,每次用卡的时候都用那个恰好能赢的卡是最优的,如果没有就用最小的,然后枚举一下改规则的时间,这样可以O(N^2)算出答案

我们考虑先不枚举改规则的中间点,用F[i]表示前i个回合不改规则最多能赢多少个,这个可以直接扫一遍得出答案,在设G[i]为从i开始改规则后面(包括i)能赢多少个,这个同样扫一遍就能知道答案,那么最终答案就是最大的F[i]+G[i+1]

为什么呢?首先F和G分别都是在没有手牌限制的情况下求出的答案,所以F+G一定不会比答案更差

其次,F和G方案中可能会有重复的手牌,但是可以经过调整变成不重复的手牌且不让答案变得更差(因为前面点大获胜,后面点小获胜),所以F+G一定不会比答案更优

所以F+G的最大值就是答案


(我写的是线段树,但是实际上用set就可以过....)


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int zyh[N],sxh[N],a[N];
int mi[N<<2],l[N<<2],r[N<<2],t[N<<2],b[N];
void pup(int x)
{
    mi[x]=min(mi[x<<1],mi[x<<1|1]);
}
void pud(int x)
{
    if(!t[x]) return;
    int i;
    for(i=0;i<2;i++)
    {
        mi[x<<1|i]+=t[x];
        t[x<<1|i]+=t[x];
    }
    t[x]=0;
}
void build(int now,int ll,int rr)
{
    l[now]=ll;r[now]=rr;
    if(ll==rr)
    {
        mi[now]=b[ll];
        return;
    }
    int mid=(ll+rr)>>1;
    build(now<<1,ll,mid);
    build(now<<1|1,mid+1,rr);
    pup(now);
}
int checkmi(int now,int ll,int rr)
{
    if(l[now]==ll&&r[now]==rr) return mi[now];
    pud(now);
    int mid=(l[now]+r[now])>>1;
    if(rr<=mid) return checkmi(now<<1,ll,rr);
    else if(ll>mid) return checkmi(now<<1|1,ll,rr);
    else return min(checkmi(now<<1,ll,mid),checkmi(now<<1|1,mid+1,rr));
}
void change(int now,int ll,int rr,int v)
{
    if(l[now]==ll&&r[now]==rr)
    {
        t[now]+=v;
        mi[now]+=v;
        return;
    }
    pud(now);
    int mid=(l[now]+r[now])>>1;
    if(rr<=mid) change(now<<1,ll,rr,v);
    else if(ll>mid) change(now<<1|1,ll,rr,v);
    else
    {
        change(now<<1,ll,mid,v);
        change(now<<1|1,mid+1,rr,v);
    }
    pup(now);
}
int ff[N],gg[N];
int main()
{
    int n;
    scanf("%d",&n);
    int i,j,x,y;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&zyh[i]);
        a[i]=zyh[i];
    }
    sort(zyh+1,zyh+n+1);
    int now=1,cn=0;
    for(i=1;i<=2*n;i++)
    {
        if(zyh[now]==i) now++;
        else
        {
            cn++;
            sxh[cn]=i;
        }
    }
    now=1;
    b[0]=0;
    for(i=1;i<=n;i++)
    {
        b[i]=b[i-1]-1;
        while(zyh[now]<sxh[i]&&now<=n) now++,b[i]++;
    }
    build(1,1,n);
    int L,R,mid;
    for(i=n;i>=1;i--)
    {
        ff[n-i]=n+checkmi(1,n-i+1,n);
        L=n-i+1;R=n;
        if(a[i]<sxh[n])
        {
            while(L<R)
            {
                mid=(L+R)>>1;
                if(sxh[mid]<a[i]) L=mid+1;
                else R=mid;
            }
            change(1,L,n,-1);
        }
    }
    memset(t,0,sizeof(t));
    for(i=1;i<=n;i++)
    {
        a[i]=2*n+1-a[i];
        zyh[i]=2*n+1-zyh[i];
        sxh[i]=2*n+1-sxh[i];
    }
    for(i=1;i<=n/2;i++)
    {
        swap(zyh[i],zyh[n+1-i]);
        swap(a[i],a[n-i+1]);
        swap(sxh[i],sxh[n+1-i]);
    }
    now=1;
    for(i=1;i<=n;i++)
    {
        b[i]=b[i-1]-1;
        while(zyh[now]<sxh[i]&&now<=n) now++,b[i]++;
    }
    build(1,1,n);
    for(i=n;i>=1;i--)
    {
        gg[i]=n+checkmi(1,n-i+1,n);
        L=n-i+1;R=n;
        if(a[i]<sxh[n])
        {
            while(L<R)
            {
                mid=(L+R)>>1;
                if(sxh[mid]<a[i]) L=mid+1;
                else R=mid;
            }
            change(1,L,n,-1);
        }
    }
    int ans=0;
    for(i=0;i<=n;i++)
    ans=max(ans,ff[i]+gg[i]);
    printf("%d",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值