初十hu测 T3.deep(点分治)

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

分析:

部分分写一个链剖

线段树中维护信息:
sum s u m :区间和(“(”赋值为1,“)”赋值为-1)
lmx l m x :从左端开始的区间最大值
rmx r m x :从右端开始的区间最大值
lmn l m n :从左端开始的区间最小值
rmn r m n :从右端开始的区间最小值
L L :未匹配的左括号
R:未匹配的右括号

简单看一下核心代码:

Tree update(Tree l,Tree r) {
    Tree ans;
    ans.sum=l.sum+r.sum;
    ans.lmn=min(l.lmn,l.sum+r.lmn);
    ans.lmx=max(l.lmx,l.sum+r.lmx);
    ans.rmn=min(r.rmn,r.sum+l.rmn);
    ans.rmx=max(r.rmx,r.sum+l.rmx);
    ans.L=max(0,l.L-r.R)+r.L;
    ans.R=max(0,r.R-l.L)+l.R;
    return ans;
}

void ask(int x,int y) {
    Tree X,Y;
    clear(X); clear(Y);
    int f1=top[x],f2=top[y];
    while (f1!=f2) {
        if (deep[f1]>deep[f2]) {
            X=update(X,ask_T(1,1,n,num[f1],num[x]));
            x=pre[f1]; f1=top[x];
        } else {
            Y=update(Y,ask_T(1,1,n,num[f2],num[y]));
            y=pre[f2]; f2=top[y];
        }
    }
    if (num[x]>num[y]) X=update(X,ask_T(1,1,n,num[y],num[x]));
    else Y=update(Y,ask_T(1,1,n,num[x],num[y]));

    int maxx=0;
    if (X.L==Y.R&&X.R==0&&Y.L==0) 
        maxx=max(maxx,max(X.lmx,X.sum+Y.lmx));
    if (X.R==Y.L&&X.L==0&&Y.R==0) 
        maxx=max(maxx,max(Y.lmx,Y.sum+X.lmx));

    ANS=max(ANS,maxx);
}

想清楚就好了
虽然链剖完之后,路径上的两条链可能方向不一样
但是不影响未匹配的左括号和右括号个数(举例画图就可以理解了)

100%

题解写的太简单了:
这里写图片描述

不是很明白,只能从代码中慢慢琢磨一下

找重心还是很naive的

void solve(int now) {
    cal(now);
    vis[now]=1;
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y])
        {
            sz=size[way[i].y];
            root=0;
            findroot(way[i].y,now);
            solve(root);
        }
}

void findroot(int now,int fa) {
    size[now]=1;
    f[now]=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa&&!vis[way[i].y]) {
            findroot(way[i].y,now);
            size[now]+=size[way[i].y];
            f[now]=max(f[now],size[way[i].y]);
        }
    f[now]=max(f[now],sz-size[now]);
    if (f[now]<f[root]) root=now;
}

重点就在于cal:
处理root的所有子结点
之后我惊奇的发现,这样处理可以使得到答案的左右两条链一定在两个不同的子树中,不用去重了
dfs_1就是专门计算答案的:当前子树和之前遍历过的子树之间形成的答案
dfs_2:把当前子树的信息统计一下

void cal(int now) {
    top=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y])
            S[++top]=way[i].y;
    for (int i=1;i<=top;i++) {
        dfs_1(S[i],root,0,0,0);
        dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
    }
    clear(root,0,0,0,0);
    for (int i=top;i>=1;i--) {
        dfs_1(S[i],root,0,0,0);
        dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
    }
    clear(root,0,0,0,0);
}
dfs_1

lmx l m x :最大前缀和(嵌套层数)
rmx r m x :左括号数
sum s u m :区间和(左括号数量和右括号数量的差值)

如果 rmx<=0 r m x <= 0 ,说明肯定有多余的右括号,那么当前的嵌套层数应该是右括号的前缀和: lmxsum(sum<0) l m x − s u m ( s u m < 0 )
(sum:右括号比左括号多的数量)

如果 sum==1 s u m == − 1 ,而当前结点上的恰是左括号,说明当前就能形成一个合法的括号序列,维护一下答案

然而真正的难点在于mp的含义:
mp[sum][0] m p [ s u m ] [ 0 ] :区间和是sum这种状态是否到达过
mp[sum][1] m p [ s u m ] [ 1 ] :区间和是sum,最大的嵌套层数
且mp中的sum只针对左括号较多的情况

void dfs_1(int now,int fa,int lmx,int rmx,int sum) {
    lmx=max(sum+val[now],lmx);    
    rmx=max(rmx+val[now],0);
    sum+=val[now];
    if (rmx<=0) {
        if (mp[-sum][0]&&ans<max(mp[-sum][1],lmx-sum))
            ans=max(mp[-sum][1],lmx-sum);
        if (sum==-1&&val[root]==1&&ans<max(1,lmx+1))
            ans=max(1,lmx+1);
    }
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y]&&way[i].y!=fa)
            dfs_1(way[i].y,now,lmx,rmx,sum);
}
dfs_2

lmx l m x :左括号数量
lmn l m n :右括号数量(负数)

lmn>=0 l m n >= 0 (实际上就是 lmn==0 l m n == 0 ),说明没有右括号

void dfs_2(int now,int fa,int lmx,int lmn,int sum) {
    lmx=max(lmx+val[now],0);
    lmn=min(lmn+val[now],0);
    sum+=val[now];
    if (lmn>=0) {
        mp[sum][0]=1;
        if (mp[sum][1]<lmx)
            mp[sum][1]=lmx;
        if (sum==0&&ans<mp[sum][1])
            ans=mp[sum][1];
    }
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y]&&way[i].y!=fa)
            dfs_2(way[i].y,now,lmx,lmn,sum);
}
clear

lmx l m x :左括号数量
lmn l m n :右括号数量

void clear(int now,int fa,int lmx,int lmn,int sum) {
    lmx=max(lmx+val[now],0);
    lmn=min(lmn+val[now],0);
    sum+=val[now];
    if (lmn>=0)
        mp[sum][0]=mp[sum][1]=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y]&&way[i].y!=fa)
            clear(way[i].y,now,lmx,lmn,sum);
}

tip

在后来回味的时候,突然发现这种统计答案的方法类似很久之前zyh学长讲的某种“树上玄学二分”

#include<bits/stdc++.h>

using namespace std;

const int N=100005;
struct node{
    int y,nxt;
};
node way[N<<1];
int n,sz,root,st[N],size[N],f[N],tot=0;
int ans=0,val[N],mp[N][2];
bool vis[N];

void add(int u,int w) {
    tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}

void findroot(int now,int fa) {
    size[now]=1;
    f[now]=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa&&!vis[way[i].y]) {
            findroot(way[i].y,now);
            size[now]+=size[way[i].y];
            f[now]=max(f[now],size[way[i].y]);
        }
    f[now]=max(f[now],sz-size[now]);
    if (f[now]<f[root]) root=now;
}

int S[N],top;

void dfs_1(int now,int fa,int lmx,int rmx,int sum) {
    lmx=max(sum+val[now],lmx);    
    rmx=max(rmx+val[now],0);
    sum+=val[now];
    if (rmx<=0) {
        if (mp[-sum][0]&&ans<max(mp[-sum][1],lmx-sum))
            ans=max(mp[-sum][1],lmx-sum);
        if (sum==-1&&val[root]==1&&ans<max(1,lmx+1))
            ans=max(1,lmx+1);
    }
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y]&&way[i].y!=fa)
            dfs_1(way[i].y,now,lmx,rmx,sum);
}

void dfs_2(int now,int fa,int lmx,int lmn,int sum) {
    lmx=max(lmx+val[now],0);
    lmn=min(lmn+val[now],0);
    sum+=val[now];
    if (lmn>=0) {
        mp[sum][0]=1;
        if (mp[sum][1]<lmx)
            mp[sum][1]=lmx;
        if (sum==0&&ans<mp[sum][1])
            ans=mp[sum][1];
    }
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y]&&way[i].y!=fa)
            dfs_2(way[i].y,now,lmx,lmn,sum);
}

void clear(int now,int fa,int lmx,int lmn,int sum) {
    lmx=max(lmx+val[now],0);
    lmn=min(lmn+val[now],0);
    sum+=val[now];
    if (lmn>=0)
        mp[sum][0]=mp[sum][1]=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y]&&way[i].y!=fa)
            clear(way[i].y,now,lmx,lmn,sum);
}

void cal(int now) {
    top=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y])
            S[++top]=way[i].y;
    for (int i=1;i<=top;i++) {
        dfs_1(S[i],root,0,0,0);
        dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
    }
    clear(root,0,0,0,0);
    for (int i=top;i>=1;i--) {
        dfs_1(S[i],root,0,0,0);
        dfs_2(S[i],root,max(0,val[root]),min(0,val[root]),val[root]);
    }
    clear(root,0,0,0,0);
}

void solve(int now) {
    cal(now);
    vis[now]=1;
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y])
        {
            sz=size[way[i].y];
            root=0;
            findroot(way[i].y,now);
            solve(root);
        }
}

int main()
{
    scanf("%d",&n);
    for (int i=2;i<=n;i++) {
        int x;
        scanf("%d",&x);
        add(x,i);
    }
    char s[3];
    for (int i=1;i<=n;i++) {
        scanf("%s",s);
        if (s[0]=='(') val[i]=1;
        else val[i]=-1;
    }
    sz=n; root=0; f[0]=n;
    findroot(1,0);
    solve(root);
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值