决斗(线段树)

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

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

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

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

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

1 ≤ n , a i , b i ≤ 1 0 5 1\le n,a_i,b_i\le10^5 1n,ai,bi105


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

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

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

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

具体实现参照代码。

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

#include
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));
        }
    }
}

你可能感兴趣的:(算法,数据结构)