[JZOJ4684]卡牌游戏

    xiaoxiao2025-06-07  44

    题目描述

    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; }
    转载请注明原文地址: https://ju.6miu.com/read-1299710.html
    最新回复(0)