决斗 题解

决斗 题解

赛题来自 OIFHA 第四场模拟赛。

原题展现

青蛙哥与名侦探柯南正在进行一场对决。

他们两个人每人有 n n n 张牌,每张牌有一个点数。

并且在接下来的 n n n 个回合中每回合青蛙哥与名侦探柯南两人会各自打出一张牌。

每回合裁判会检查,打出的牌点数更高的一方获胜从而得到一分,如果二人点数相同,则不得分。

然而现在青蛙哥通过偷看的方法得到了名侦探柯南的出牌顺序,他可以任意定一个自己的出牌的顺序。

他首先希望让自己的得分尽可能高,然后就是希望在让自己的得分尽可能高这个前提下,最大化自己从第一回合开始到最后一个回合结束过程中,每回合出牌点数构成的字符串的字典序。

数据范围: n , a i ≤ 1 0 5 n,a_i\leq 10^5 n,ai105

Solution

首先假设我们已经得知青蛙最高得分是多少。

对于第 i i i 回合,考虑让不让青蛙在这个回合赢。

如果赢的话,可以取最大的 b j b_j bj,也可以取只比 a i a_i ai 大一点的 b j b_j bj

如果不让他赢,可以取最小的 b j b_j bj,也可以取只比 a i a_i ai 小一点的 b j b_j bj

可见,让青蛙在这个回合赢或输,可取的值都是一个 [ l , r ] [l,r] [l,r] 区间。题目要求字典序最大,而我们也不能直接确定青蛙在这个区间取多少时字典序最大。所以这里需要二分答案。

至于一开始的假设:“已经得知最高得分”,这个怎么实现呢?

可以考虑维护一棵权值线段树,每个节点都是一个三元组 ( s , s 1 , s 2 ) (s,s_1,s_2) (s,s1,s2)。分别表示:青蛙哥的得分,柯南的牌数,青蛙的牌数。

在合并区间的时候,肯定是拿青蛙点数大的牌去对抗柯南点数小的牌。得分就是 tr[rt].s=tr[lson].s+tr[rson].s+k,其中 k=min(tr[lson].s1,tr[rson].s2)

时间复杂度 O ( n log ⁡ 2 V ) O(n\log^2 V) O(nlog2V) V V V 是值域。

code

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define ls rt<<1
#define rs ls|1

const int MAXN=1e5+5,m=1e5;

int n;
int a[MAXN],b[MAXN];
struct tree
{
    int s,s1,s2;
}tr[MAXN<<2];
multiset<int> s;

void pushup(int rt)
{
    int minz=min(tr[ls].s1,tr[rs].s2);
    tr[rt].s=tr[ls].s+tr[rs].s+minz;
    tr[rt].s1=tr[ls].s1+tr[rs].s1-minz;
    tr[rt].s2=tr[ls].s2+tr[rs].s2-minz;
}

void update(int rt,int l,int r,int x,int v1,int v2)
{
    if(l==r)
    {
        tr[rt].s1+=v1;
        tr[rt].s2+=v2;
        return;
    }
    int mid=l+r>>1;
    if(x<=mid) update(ls,l,mid,x,v1,v2);
    else update(rs,mid+1,r,x,v1,v2);
    pushup(rt);
}

signed main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&b[i]),s.insert(b[i]);

    for(int i=1;i<=n;i++)
        update(1,1,m,a[i],1,0),update(1,1,m,b[i],0,1);

    int cnt=tr[1].s;
    for(int i=1;i<=n;i++)
    {
        update(1,1,m,a[i],-1,0);
        int l=a[i]+1,r=*s.rbegin(),mid,ans;
        while (l<=r)
        {
            mid=l+r>>1;
            update(1,1,m,mid,0,-1);
            if(tr[1].s+1==cnt) l=mid+1,ans=mid;
            else r=mid-1;
            update(1,1,m,mid,0,1);
        }
        update(1,1,m,ans,0,-1);
        if(a[i]<*s.rbegin()&&tr[1].s+1==cnt)
        {
            --cnt;
            printf("%lld ",ans);
            s.erase(s.find(ans));
        }
        else
        {
            update(1,1,m,ans,0,1);
            l=1,r=a[i],ans=l;
            while (l<=r)
            {
                mid=l+r>>1;
                update(1,1,m,mid,0,-1);
                if(tr[1].s==cnt) l=mid+1,ans=mid;
                else r=mid-1;
                update(1,1,m,mid,0,1);
            }
            update(1,1,m,ans,0,-1);
            printf("%lld ",ans);
            s.erase(s.find(ans));
        }
    }

    return 0;
}
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值