决斗(线段树)

文章讨论了青蛙哥在已知名侦探柯南出牌顺序的情况下,如何制定策略以最大化得分并保证每回合出牌点数字典序最大化的数学问题。利用线段树数据结构解决动态规划问题,时间复杂度为O(nlog^2V)。

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

青蛙哥与名侦探柯南正在进行一场对决。他们两个人每人有 nnn 张牌,每张牌有一个点数。

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

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

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

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

1≤n,ai,bi≤1051\le n,a_i,b_i\le10^51n,ai,bi105


假设我们已经求出青蛙最多得 kkk 分,那么下面按顺序考虑填入什么数字能使字典序最大。

对于第 iii 回合,如果我们要赢,可以出最大的,但是如果这么做可能会影响后面需要它来赢的回合;如果我们要输,可以出最小的,但是字典序就不一定最大。容易得到出牌点数在一个区间 [l,r][l,r][l,r] 内。

这时可以二分求出我们能填的最大点数,然后判断出这张是否会影响得分。

判断的实现可以用线段树。建权值线段树,每个节点维护三元组 (s,s1,s2)(s,s_1,s_2)(s,s1,s2),分别表示 当前值域的得分、柯南剩的牌数、青蛙剩的牌数。在向上合并信息时,青蛙可以用自己的大牌去对柯南的小牌,青蛙的得分是 Rs2−Ls1R_{s_2}-L_{s_1}Rs2Ls1,就是用右区间青蛙牌数减去左区间柯南牌数。s1,s2s_1,s_2s1,s2 的维护显然。

具体实现参照代码。

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

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
int n,a[N],b[N];
multiset<int> s;
struct node
{
    int s,s1,s2;//a b
}tr[N<<2];
void pushup(int rt)
{
    int x=min(tr[rt<<1|1].s2,tr[rt<<1].s1);
    tr[rt].s=tr[rt<<1].s+tr[rt<<1|1].s+x;
    tr[rt].s1=tr[rt<<1].s1+tr[rt<<1|1].s1-x;
    tr[rt].s2=tr[rt<<1].s2+tr[rt<<1|1].s2-x;
}
void update(int rt,int l,int r,int x,int d1,int d2)
{
    if(l==r){
        tr[rt].s1+=d1;
        tr[rt].s2+=d2;
        return;
    }
    int mid=l+r>>1;
    if(x<=mid) update(rt<<1,l,mid,x,d1,d2);
    else update(rt<<1|1,mid+1,r,x,d1,d2);
    pushup(rt);
}
int main()
{
    // freopen("duel.in","r",stdin);
    // freopen("duel.out","w",stdout);
    cin.tie(0)->sync_with_stdio(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],update(1,1,100000,a[i],1,0);
    for(int i=1;i<=n;i++) cin>>b[i],update(1,1,100000,b[i],0,1),s.insert(b[i]);
    int ans=tr[1].s;//剩余回合需得分数
    for(int i=1;i<=n;i++){
        update(1,1,100000,a[i],-1,0);//删去ai,如果不会对得分有影响,tr[1].s恒为ans,即二分不起作用
        int l=a[i]+1,r=*s.rbegin(),Ans;
        while(l<=r){
            int mid=l+r>>1;
            update(1,1,100000,mid,0,-1);
            if(tr[1].s+1==ans) l=mid+1,Ans=mid;//如果有影响分数,就尝试提高点数
            else r=mid-1;
            update(1,1,100000,mid,0,1);
        }
        update(1,1,100000,Ans,0,-1);
        if(a[i]<*s.rbegin()&&tr[1].s+1==ans){//二分的答案Ans合法,此回合得分
            ans--;
            cout<<Ans<<" ";
            s.erase(s.find(Ans));
        }
        else{//此回合不能得分
            update(1,1,100000,Ans,0,1);
            l=1,r=a[i],Ans=l;//青蛙的牌不超过ai
            while(l<=r){
                int mid=l+r>>1;
                update(1,1,100000,mid,0,-1);
                if(tr[1].s==ans) l=mid+1,Ans=mid;//如果不影响分数,就尝试提高点数
                else r=mid-1;
                update(1,1,100000,mid,0,1);
            }
            update(1,1,100000,Ans,0,-1);
            cout<<Ans<<" ";
            s.erase(s.find(Ans));
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值