[JZOJ4684]卡牌游戏

这是一篇关于解决OI问题[JZOJ4684]卡牌游戏的文章。通过分析,作者指出游戏的一个明显性质是前i局会用掉前i大的牌。采用贪心策略,前半段比赛用最小的牌匹配对方的牌,后半段类似。文章利用线段树来处理两个独立的部分,并详细解释了如何用线段树维护区间内的未匹配牌数。最后,通过扫描分割位置得到最终的获胜局数。整体时间复杂度为O(nlog2n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

题目描述
1n50000


题目分析

一个显然的性质,假设有i局是使用第一个规则,那么这一部分一定用掉前i大的牌。
贪心也是十分显然的,前半段中,一张对方的牌必须由己方中能打掉这张牌的数值最小的牌来匹配。后半段类似。
由于前后两部分求解是互不影响的,我们考虑使用前缀和后缀的方式来分别处理两部分。
由于是排列,所以我们可以使用线段树维护每一个数值区间内未匹配牌数(己方)和未被匹配牌数(对方)。这个简单讨论一下就可以合并。那么赢的局数就是总局数减去未匹配数。
前半部分位置i我们每次加入对方第i张牌以及己方第i大的牌,线段树维护一下答案。后半部分的话我们先将排列翻转,就可以将小的牌打大的牌变为和前半部分一样的大打小,使用相同方法维护。
最后扫一遍分割位置,前缀后缀相加更新答案。
时间复杂度O(nlog2n)
原题貌似是某USACO金组题。


代码实现

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=50050;
const int A=N<<1;
const int S=A<<2;

struct segment_tree
{
    int L[S],R[S],W[S];

    void update(int x)
    {
        L[x]=L[x<<1],R[x]=R[x<<1|1],W[x]=W[x<<1]+W[x<<1|1]+min(R[x<<1],L[x<<1|1]);
        if (R[x<<1]<=L[x<<1|1]) L[x]+=L[x<<1|1]-R[x<<1];
        else R[x]+=R[x<<1]-L[x<<1|1];
    }

    void modify(int x,int y,int l,int r,bool tp)
    {
        if (l==r)
        {
            if (tp) L[x]=1,R[x]=W[x]=0;
            else R[x]=1,L[x]=W[x]=0;
            return;
        }
        int mid=l+r>>1;
        if (y<=mid) modify(x<<1,y,l,mid,tp);
        else modify(x<<1|1,y,mid+1,r,tp);
        update(x);
    }
}t;

int card[N],cme[N],pre[N],suf[N];
bool exist[A];
int n,a,ans;

int main()
{
    freopen("cardgame.in","r",stdin),freopen("cardgame.out","w",stdout);
    n=read(),a=n<<1;
    for (int i=1;i<=n;i++) exist[card[i]=read()]=1;
    for (int i=1;i<=a;i++) if (!exist[i]) cme[++cme[0]]=i;
    for (int i=1;i<=n;i++)
    {
        t.modify(1,card[i],1,a,0),t.modify(1,cme[n-i+1],1,a,1);
        pre[i]=t.W[1];
    }
    for (int i=1;i<=n;i++) card[i]=a-card[i]+1,cme[i]=a-cme[i]+1;
    memset(t.L,0,sizeof t.L),memset(t.R,0,sizeof t.R),memset(t.W,0,sizeof t.W);
    for (int i=n;i>=1;i--)
    {
        t.modify(1,card[i],1,a,0),t.modify(1,cme[n-i+1],1,a,1);
        suf[i]=t.W[1];
    }
    ans=0;
    for (int i=0;i<=n;i++) ans=max(ans,pre[i]+suf[i+1]);
    printf("%d\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值