[ABC218G] Game on Tree 2

思路

part 1 转移方程

对于每一个节点而言,如果是小T推动,那么下一个推动的一定是小J,小J推的情况同理。也就是说,每一个点推动的人是固定的,而叶子节点的中位数是固定且可以求出的。

那么对于叶子节点的父亲来说,若小T推,则会推向最大的点;反之推向最小的点。那么在考虑叶子节点的爷爷时可以把叶子节点的父亲当做新的叶子节点(因为走到它这个点会得到的中位数已经固定的)。依次类推,变可以得到转移方程。

f [ i ] f[i] f[i] 表示 i i i 节点的最优选择(即小T选最大,小J选最小)。

i i i 点小T推动
f [ i ] = max ⁡ ( f [ j ] ) f[i]=\max(f[j]) f[i]=max(f[j])
其中 j j j i i i 的儿子节点。

i i i 点小J推动
f [ i ] = min ⁡ ( f [ j ] ) f[i]=\min(f[j]) f[i]=min(f[j])
其中 j j j i i i 的儿子节点。

i i i 点为叶子节点 f [ i ] f[i] f[i] 为该叶子节点的中位数值。

part 2 中位数

下面介绍求中位数的 3 3 3 种方法。

方法 1 对顶堆

开一个大根堆,一个小根堆,且两堆元素个数相差最大为 1 1 1 。若已知中位数,则大于中位数的放入小根堆,小于中位数的放入大根堆,接着维护两个元素差最大为 1 1 1 的性质,即大根堆多了,将大根堆堆顶向小根堆加入,小根堆同理,那么可以保证两堆对顶的数肯定有一个为中位数(删除需要假删除,步骤同理)。其实一开始我们只有一个数的时候中位数就是这一个数,所以使用该结构即可在 log ⁡ n \log_n logn 的时间内完成求中位数。

方法 2 平衡树

使用平衡树,实现插入、删除、求排名操作,即可在 log ⁡ n \log_n logn 的时间完成求中位数。

详见平衡树模板P3369

方法 3 线段树

可以使用动态开点的线段树,或者离散化后使用线段树求排名。

具体实现请 bdfs

CODE

实现给出的是动态开点线段树的实现方法。

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

#define inf 1e9
#define lch(i) tree[i].lch
#define rch(i) tree[i].rch
#define ll long long

const ll maxn=2e5+5;

struct node
{
    ll to,nxt;
}edge[maxn*2];
struct node1
{
    ll l,r,lch,rch,val;
}tree[maxn*31];

ll n,tot=1,cnt;
ll f[maxn],a[maxn],head[maxn];

void add(ll x,ll y)
{
    cnt++;
    edge[cnt].to=y;
    edge[cnt].nxt=head[x];
    head[x]=cnt;
}

inline void updata(ll p){tree[p].val=tree[lch(p)].val+tree[rch(p)].val;}
inline void insert(ll p,ll l,ll r,ll x,ll v)//动态开点
{
    tree[p].l=l;
    tree[p].r=r;
    if(l==r)
    {
        tree[p].val+=v;
        return ;
    }

    ll mid=(l+r)/2;
    if(x<=mid)
    {
        if(!lch(p)) lch(p)=++tot;
        insert(lch(p),l,mid,x,v);
    }
    else
    {
        if(!rch(p)) rch(p)=++tot;
        insert(rch(p),mid+1,r,x,v);
    }
    updata(p);
}
inline ll gtrk(ll p,ll rk)//求排名
{
    if(tree[p].l==tree[p].r) return tree[p].l;
    if(tree[lch(p)].val>=rk) return gtrk(lch(p),rk);
    else return gtrk(rch(p),rk-tree[lch(p)].val);
}

inline void dfs(ll x,ll bj,ll fa,ll c)
{
    f[x]=-inf;
    insert(1,1,inf*2,a[x],1);
    ll kid=0;
    for(ll i=head[x];i;i=edge[i].nxt)
    {
        ll v=edge[i].to;
        if(v!=fa) {dfs(v,-bj,x,c+1);kid++;}
    }
    if(kid)//非叶子节点
    {
        for(ll i=head[x];i;i=edge[i].nxt)
        {
            ll v=edge[i].to;
            if(v==fa) continue;
            f[x]=max(f[v]*bj,f[x]);
        }
        f[x]=abs(f[x]);
    }
    else//叶子节点
    {
        if(c%2==0) f[x]=(gtrk(1,c/2)+gtrk(1,c/2+1))/2;
        else f[x]=gtrk(1,(c+1)/2);
    }
    insert(1,1,inf*2,a[x],-1);
}

int main()
{
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(ll i=1;i<n;i++)
    {
        ll x,y;
        scanf("%lld%lld",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(1,1,1,1);
    printf("%lld",f[1]);
}

ending……

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值