[JSOI2016]轻重路径

10 篇文章 0 订阅
6 篇文章 0 订阅

题目大意

给定一颗二叉树。
每次删除一个叶子节点,并要求输出所有重链指向结点编号之和。
初始也要输出一次。
初始时,如果一个结点的两个儿子大小相同,则选择左儿子为重儿子。
一次删除操作后,如果一个结点的两个儿子大小相同,则不改变原先的重儿子选择。

发现

如果删除一个叶子,重儿子指向可能会发生改变的一定是该结点到根节点的路径上的结点。
其中表现为——路径上的重边可能会变成轻边。
这个就不好处理……
如果是增加一个叶子,会有什么影响?
其中表现为——路径上的轻边可能会变成重边。
这个就好处理了!因为轻边数量<=log n。
只要沿着重链往上跳,每遇上一条轻边就看看它会不会变成重边即可。
可本题是删除……
别怕!我们先把结点插完,然后倒着处理。就把原来的删除操作改成添加操作了。
因为要动态维护子树size,并且要修改轻重边,所以我们用动态树链剖分(即用splay维护重链,注意这不是LCT,因为重链是严格意义上的重链,而且没有access操作)。

半强制在线?

万一出现了两个子树大小相同,我们怎么知道哪个是重儿子呢?因为我们是倒着做的……
其实也很简单。
例如结点i的两个儿子j和k此时大小相同。
下一个插入进以j为根的子树中的结点的插入时间为t1。
下一个插入进以k为根的子树中的结点的插入时间为t2。
我们定义插入时间小的会被率先插入。
如果 t1<t2 ,显然j是重儿子。
如果 t1>t2 ,显然k是重儿子。
如果 t1=t2 ?谁是左儿子谁就是重儿子。
然后如何获得t1和t2呢?
因为已经知道最终树的形态,以及每个结点的插入时间。
那么,我们就是要查询子树最小值。
dfs序就可以搞了。注意每插入一个结点后要把其的插入时间改为无穷。
我的代码的实现,每个结点其实保留的是删除时间而非插入时间。
然后本题解决,然而我spaly大法被卡常最后90分。
有许多细节……

#include<cstdio>
#include<algorithm>
#include<ctime>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=200000+10;
int size[maxn],father[maxn],pp[maxn],zx[maxn],tree[maxn][2],add[maxn];
int fa[maxn],a[maxn],h[maxn],go[maxn],next[maxn],sta[maxn],del[maxn];
int big[maxn],dfn[maxn],num[maxn*5];
int left[maxn],right[maxn],lea[maxn];
bool bz[maxn],lch[maxn];
int i,j,k,l,t,n,m,tot,top,cnt,root;
ll ans,s[maxn];
void insert(int x,int y){
    fa[y]=x;
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
int pd(int x){
    return x==tree[father[x]][1];
}
void rotate(int x){
    int y=father[x],z=pd(x);
    father[x]=father[y];
    if (father[y]) tree[father[y]][pd(y)]=x;
    tree[y][z]=tree[x][1-z];
    if (tree[x][1-z]) father[tree[x][1-z]]=y;
    tree[x][1-z]=y;
    father[y]=x;
    if (pp[y]) pp[x]=pp[y],pp[y]=0;
}
void mark(int x,int y){
    size[x]+=y;
    add[x]+=y;
}
void clear(int x){
    if (add[x]){
        if (tree[x][0]) mark(tree[x][0],add[x]);
        if (tree[x][1]) mark(tree[x][1],add[x]);
        add[x]=0;
    }
}
void remove(int x,int y){
    int top=0;
    while (x!=y){
        sta[++top]=x;
        x=father[x];
    }
    while (top){
        clear(sta[top]);
        top--;
    }
}
void splay(int x,int y){
    remove(x,y);
    while (father[x]!=y){
        if (father[father[x]]!=y) 
            if (pd(x)==pd(father[x])) rotate(father[x]);else rotate(x);
        rotate(x);
    }
}
void link(int x,int y){
    pp[x]=y;
    while (x){
        splay(x,0);
        size[x]++;
        if (tree[x][0]) mark(tree[x][0],1);
        x=pp[x];
    }
}
void change(int p,int l,int r,int a,int b){
    if (l==r){
        num[p]=b;
        return;
    }
    int mid=(l+r)/2;
    if (a<=mid) change(p*2,l,mid,a,b);else change(p*2+1,mid+1,r,a,b);
    num[p]=max(num[p*2],num[p*2+1]);
}
int query(int p,int l,int r,int a,int b){
    if (l==a&&r==b) return num[p];
    int mid=(l+r)/2;
    if (b<=mid) return query(p*2,l,mid,a,b);
    else if (a>mid) return query(p*2+1,mid+1,r,a,b);
    else return max(query(p*2,l,mid,a,mid),query(p*2+1,mid+1,r,mid+1,b));
}
int nexttime(int x){
    return query(1,1,n,dfn[x],dfn[x]+big[x]-1);
}
void solve(int x){
    int y,z,t,j,k;
    while (x!=root){
        splay(x,0);
        y=pp[x];
        splay(y,0);
        ans-=(ll)zx[y];
        clear(y);
        father[tree[y][1]]=0;
        pp[tree[y][1]]=y;
        tree[y][1]=0;
        if (!zx[y]) zx[y]=x;
        else{
            if (2*size[x]>size[y]-1) zx[y]=x;
            else if (2*size[x]==size[y]-1){
                t=h[y];
                while (t){
                    if (go[t]!=x){
                        z=go[t];
                        break;
                    }
                    t=next[t];
                }
                j=nexttime(x);k=nexttime(z);
                if (j>k) zx[y]=x;
                else if (j==k&&lch[x]) zx[y]=x;
            }
        }
        ans+=(ll)zx[y];
        splay(zx[y],0);
        tree[y][1]=zx[y];
        father[zx[y]]=y;
        pp[zx[y]]=0;
        x=y;
        splay(x,0);
        while (tree[x][0]) x=tree[x][0];
    }
}
void dfs(int x){
    int t=h[x];
    while (t){
        if (!bz[go[t]]){
            link(go[t],x);
            dfs(go[t]);
        }
        t=next[t];
    }
    splay(x,0);
    t=h[x];
    while (t){
        if (!bz[go[t]]){
            splay(go[t],0);
            if (2*size[go[t]]>size[x]-1) zx[x]=go[t];
            else if (2*size[go[t]]==size[x]-1&&lch[go[t]]) zx[x]=go[t];
        }
        t=next[t];
    }
    ans+=(ll)zx[x];
}
void dg(int x){
    dfn[x]=++cnt;
    int t=h[x];
    while (t){
        dg(go[t]);
        big[x]+=big[go[t]];
        t=next[t];
    }
    big[x]++;
}
void brute(){
    fo(i,1,n) size[i]=big[i];
    fo(i,1,n){
        if (fa[i]){
            if (lch[i]) left[fa[i]]=i;else right[fa[i]]=i;
            lea[fa[i]]++;
        }
    }
    fo(i,1,n){
        if (big[left[i]]>=big[right[i]]) zx[i]=left[i];else zx[i]=right[i];
        ans+=(ll)zx[i];
    }
    printf("%lld\n",ans);
    scanf("%d",&m);
    fo(i,1,m){
        scanf("%d",&j);
        lea[fa[j]]--;
        while (j!=root){
            k=fa[j];
            big[j]--;
            if (left[k]==j) l=right[k];else l=left[k];
            ans-=(ll)zx[k];
            if (big[j]<big[l]) zx[k]=l;
            if (!lea[k]) zx[k]=0;
            ans+=(ll)zx[k];
            j=k;
        }
        big[root]--;
        printf("%lld\n",ans);
    }
}
int read(){
    int x=0;
    char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
void now(){
    printf("%d\n",clock());
}
int main(){
    freopen("heavy.in","r",stdin);freopen("heavy.out","w",stdout);
    n=read();
    fo(i,1,n){
        j=read();k=read();
        if (j){
            insert(i,j);
            lch[j]=1;
        }
        if (k) insert(i,k);
    }
    //now();
    fo(i,1,n)
        if (!fa[i]){
            root=i;
            break;
        }
    dg(root);
    //now();
    if (n<=1000){
        brute();
        fclose(stdin);fclose(stdout);
        return 0;
    }
    m=read();
    fo(i,1,m){
        a[i]=read();
        bz[a[i]]=1;
        del[a[i]]=i;
    }
    //now();
    fo(i,1,n) change(1,1,n,dfn[i],del[i]);
    //now();
    dfs(root);
    //now();
    s[++top]=ans;
    fd(i,m,1){
        j=a[i];
        change(1,1,n,dfn[j],0);
        link(j,fa[j]);
        solve(j);
        s[++top]=ans;
        //printf("%d\n",clock());
    }
    while (top){
        printf("%lld\n",s[top]);
        top--;
    }
    //now();
    fclose(stdin);fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值