【CF】P348E Pilgrims

题目链接

树的性质(O(n)写法)

很好的一道题。
当然,还有树上差分,点分治等写法。
联想树的直径,不难发现有这样一个性质:

所有教徒到离他最远的修道院的路径必定相交于一个节点。而这个节点就是一条最长路径的中点。我们称这个点为中心(特别的中点在于某条两点间的边上时,这条边的两个端点都可以作为中心)

有了这个性质,我们先找到中心,再把中心作为根节点,这样的话,所有教徒走到目的地都需要经过根节点。
然后我们再处理出根节点所有儿子的子树中距离根节点最远的修道院的距离。

接着,就是重点了。
我们枚举一个要毁掉的非修道院的点,那么,其子树中所有的教徒都无法达到目的地(无法到根节点)。
那对于子树外的点呢?
分类讨论!
有如下情况:(我们先标记根节点的儿子,若其子树中有最大深度,记为A,若有次达深度,记为B,都没有,则记为C,同时最大深度和次大深度严格不相等)
1.A和B的数量都为1
1734076-20190723140148922-1569091916.png

由图知,除了A要到B外,其他的点都是到A。
也就是说,如果断了A内的点,将会隔断除A外其他所有子树的教徒
如果断了B内的点,那么只会隔断A内的所有教徒
2.A的数量为1,B的数量大于等于2
1734076-20190723140138778-1918186073.png

由图知,A可以到2个B,而其他的点都是到A。
那么,如果断了A内的点,仍将会隔断除A外其他所有子树的教徒
而断了B内的点呢?那么A依然可以走到一个符合条件的B,也就是说,此时不会影响到其他点
3.A的数量大于等于2
1734076-20190723140113565-1231795470.png

由图知,断了A内的点,其他子树的教徒依然不会被隔断
而断了B内的点,A内的教徒还可以到另一个A,依然不会有影响
也就是说,当A的数量大于等于2时,我们只用考虑子树内有多少个教徒被隔断了即可
真的这样吗?
再来看下一个。

4.A的数量为2,B的数量为0

1734076-20190723140059599-957950327.png

显然,C中是没有教徒的(如果有的话,那么其就变成B了)
这种情况我们要特判。
因为,当隔断的点在一个A中时,另一个A中的教徒会被隔断

最后,在处理一下隔断的点在根节点时的情况即可。

最后,注意细节!注意细节!注意细节!

代码:(巨丑无比,建议不要看,最好拿去对拍)

#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
int n,m,duan1,duan2,Len_D,deep[MAXN],dis[3][MAXN],root,max_deep[MAXN],size[MAXN],MaxMax,MinMax,MaxCnt,MinCnt,sumMax[MAXN],sumMin[MAXN],ans,ans_can;
int TreeCntMax,TreeCntMin,TreeKind[MAXN],Add1,Add2;
struct node {
    int ed,v;
};
vector<node>G[MAXN];
bool mark[MAXN];
void Get_D(int x,int fa,int sum,int pos) {
    if(sum>=Len_D&&mark[x]) {
        Len_D=sum;
        if(!pos)duan1=x;
        else duan2=x;
    }
    for(int i=0; i<G[x].size(); i++) {
        int t=G[x][i].ed,v=G[x][i].v;
        if(t==fa)continue;
        Get_D(t,x,sum+v,pos);
    }
}
void BL(int x,int fa,int sum,int pos) {
    dis[pos][x]=sum;
    for(int i=0; i<G[x].size(); i++) {
        int t=G[x][i].ed,v=G[x][i].v;
        if(t==fa)continue;
        BL(t,x,sum+v,pos);
    }
}
void Get_root() { //找中心
    int st;
    for(int i=1;i<=n;i++){
        if(mark[i]){
            st=i;
            break;
        }
    }
    Get_D(st,0,0,0);
    Get_D(duan1,0,0,1);
    BL(duan1,0,0,1);
    BL(duan2,0,0,2);
    for(int i=1,cha=2e9+7; i<=n; i++) {
        if(dis[1][i]+dis[2][i]==Len_D) {
            if(abs(dis[1][i]-dis[2][i])<cha) {
                cha=abs(dis[1][i]-dis[2][i]);
                root=i;
            }
        }
    }
}
void DFS(int x,int fa) {
    for(int i=0; i<G[x].size(); i++) {
        int t=G[x][i].ed,v=G[x][i].v;
        if(t==fa)continue;
        deep[t]=deep[x]+v;
        DFS(t,x);
    }
}
void DFS2(int x,int fa) {
    if(mark[x]) {
        size[x]++;
        if(deep[x]==MaxMax)sumMax[x]++;
        if(deep[x]==MinMax)sumMin[x]++;
    }
    for(int i=0; i<G[x].size(); i++) {
        int t=G[x][i].ed;
        if(t==fa)continue;
        DFS2(t,x);
        size[x]+=size[t];
        sumMax[x]+=sumMax[t];
        sumMin[x]+=sumMin[t];
    }
}
void Get_Max(int x,int fa) {
    if(mark[x])max_deep[x]=max(max_deep[x],deep[x]);
    for(int i=0; i<G[x].size(); i++) {
        int t=G[x][i].ed;
        if(t==fa)continue;
        Get_Max(t,x);
        max_deep[x]=max(max_deep[x],max_deep[t]);
    }
}
void solve(int x,int fa,int Add,int Kind) {
    if(!mark[x]) {
        int res=0;
        res+=size[x];
        if(Kind==1) {
            if(TreeCntMax==1) {
                if(sumMax[x]==MaxCnt)res+=Add;
            }
        } else if(Kind==2) {
            if(TreeCntMax==1&&TreeCntMin==1) {
                if(sumMin[x]==MinCnt)res+=Add;
            }
        }
        if(res>ans) {
            ans=res;
            ans_can=1;
        } else if(ans==res)ans_can++;
    }
    for(int i=0; i<G[x].size(); i++) {
        int t=G[x][i].ed;
        if(t==fa)continue;
        solve(t,x,Add,Kind);
    }
}
void tepan(){
    int t1,t2;
    for(int i=0,p=1;i<G[root].size();i++){
        int t=G[root][i].ed;
        if(max_deep[t]==MaxMax){
            if(p==1)t1=t,p=-1;
            else t2=t;
        }
    }
    sumMin[t2]=sumMax[t2],sumMax[t2]=0;
    MaxCnt=sumMax[t1],MinCnt=sumMin[t2];
    TreeKind[t2]=2;
    Add1=size[t2],Add2=size[t1];
}
void Calc(int x,int fa,int &Ma,int &tot){
    if(mark[x]){
        if(deep[x]>Ma){
            Ma=deep[x];
            tot=1;
        }
        else if(deep[x]==Ma)tot++;
    }
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i].ed;
        if(t==fa)continue;
        Calc(t,x,Ma,tot);
    }
}
int main() {
    scanf("%d %d",&n,&m);
    for(int i=1,x; i<=m; i++)scanf("%d",&x),mark[x]=true;
    for(int i=1; i<=n-1; i++) {
        int x,y,z;
        scanf("%d %d %d",&x,&y,&z);
        G[x].push_back((node) {y,z});
        G[y].push_back((node) {x,z});
    }
    Get_root();
    DFS(root,0);
    Get_Max(root,0);
    for(int i=0; i<G[root].size(); i++) {
        int t=G[root][i].ed;
        if(max_deep[t]>MaxMax) {
            MinMax=MaxMax;
            MaxMax=max_deep[t];
        } else if(max_deep[t]>MinMax) {
            MinMax=max_deep[t];
        }
    }
    for(int i=0;i<G[root].size();i++){
        int t=G[root][i].ed;
        int Ma=0,tot=0;
        Calc(t,root,Ma,tot);
        if(Ma==MaxMax)MaxCnt+=tot;
        else if(Ma==MinMax)MinCnt+=tot;
    }
    for(int i=0; i<G[root].size(); i++) {
        int t=G[root][i].ed;
        if(max_deep[t]==MaxMax)TreeCntMax++,TreeKind[t]=1;
        else if(max_deep[t]==MinMax)TreeCntMin++,TreeKind[t]=2;
    }
    DFS2(root,0);
    Add1=m;
    for(int i=0; i<G[root].size(); i++) {
        int t=G[root][i].ed;
        if(TreeKind[t]==1)Add1-=size[t],Add2+=size[t];
    }
    if(TreeCntMax==2&&TreeCntMin==0){
        tepan();
        TreeCntMax=TreeCntMin=1;
    }
    for(int i=0; i<G[root].size(); i++) {
        int t=G[root][i].ed;
        if(TreeKind[t]==1)solve(t,root,Add1,1);
        else if(TreeKind[t]==2)solve(t,root,Add2,2);
        else solve(t,root,0,0);
    }
    if(!mark[root]) {
        if(m>ans) {
            ans=m;
            ans_can=1;
        } else if(ans==m)ans_can++;
    }
    cout<<ans<<" "<<ans_can;
    return 0;
}

转载于:https://www.cnblogs.com/SillyTieT/p/11231385.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值