【BZOJ】3302: [Shoi2005]树的双中心 && 2103: Fire 消防站 && 2447: 消防站

Description

Input

第一行为N,1<N<=50000,表示树的节点数目,树的节点从1到N编号。
接下来N-1行,每行两个整数U,V,表示U与V之间有一条边。
再接下N行,每行一个正整数,其中第i行的正整数表示编号为i的节点权值为W(I),树的深度<=100

Output

将最小的S(x,y)输出,结果保证不超过19^9

Sample Input

5
1 2
1 3
3 4
3 5
5
7
6
5
4

Sample Output

14

HINT

选取两个中心节点为2,3

—————————————————————————————

这道题很像树的重心只不过这里要求的重心有两个QAQ

考虑树的深度比较小 我们正常求树的重心的方法就是树的高度h

这样算下来我们因为要求两个重心 那么我们可以O(n)枚举一条边把树变成两个

再O(h)算答案 这样复杂度是可以接受的

注意我们本来的sz指的是子树内的权值和

枚举两个点之后两棵树一开始的重心默认为1棵 是1 1棵是u

然后在贪心往下找 

当然这样可能会有问题那就是因为算的是min(dis[x][u],dis[x][v])

这样的话我们本来是贪心树里面的到这个点 但是这样肯定会错

这样的话其实也影响答案 因为这个只会使当前解比真实解大

最优解的情况是不会受影响的 这样就可以解决这道问题辣

tips sum+=sz[x](x->2-n)

这样其实就是在算路径和 这个转换使代码异常优美2333

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using std::min;
using std::swap;
const int M=1e5+7;
const LL inf=0x3f3f3f3f;
char buf[M*33],*ptr=buf-1;
int read(){
    int ans=0,f=1,c=*++ptr;
    while(c<'0'||c>'9'){if(c=='-') f=-1; c=*++ptr;}
    while(c>='0'&&c<='9'){ans=ans*10+(c-'0'); c=*++ptr;}
    return ans*f;
}
LL ans=inf,sz[M],mx[M],sec[M],sum,w[M];
int T,n,fa[M],dep[M];
int first[M],cnt;
struct node{int from,to,next;}e[2*M];
void ins(int a,int b){e[++cnt]=(node){a,b,first[a]}; first[a]=cnt;}
void insert(int a,int b){ins(a,b); ins(b,a);}
void dfs(int x,int f){
    sz[x]=w[x]; mx[x]=sec[x]=0;
    for(int i=first[x];i;i=e[i].next){
        int now=e[i].to;
        if(now==f) continue;
        fa[now]=x; dep[now]=dep[x]+1;
        dfs(now,x); sz[x]+=sz[now];
        if(sz[now]>sz[mx[x]]) sec[x]=mx[x],mx[x]=now;
        else if(sz[now]>sz[sec[x]]) sec[x]=now;
    }
}
int main(){
    fread(buf,1,sizeof(buf),stdin);
    int x,y;
    n=read();
    for(int i=1;i<n;i++) x=read(),y=read(),insert(x,y);
    for(int i=1;i<=n;i++) w[i]=read();
    dep[1]=1; dfs(1,-1);
    sum=0; for(int i=2;i<=n;i++) sum+=sz[i];
    for(int i=1;i<=cnt;i+=2){
        int u=e[i].from,v=e[i].to;
        if(dep[u]<dep[v]) swap(u,v);
        LL nowh=sum;
        for(x=v;x;x=fa[x]) sz[x]-=sz[u],nowh-=sz[u];
        x=1;
        while(1){
            if(sz[mx[x]]>sz[sec[x]]&&mx[x]!=u) y=mx[x];
            else y=sec[x];
            if(sz[y]*2>sz[1]) x=y,nowh=nowh-sz[y]*2+sz[1];
            else break;
        }
        x=u;
        while(1){
            if(sz[mx[x]]*2>sz[u]) nowh=nowh-sz[mx[x]]*2+sz[u],x=mx[x];
            else break;
        }
        ans=min(ans,nowh);
        for(x=v;x;x=fa[x]) sz[x]+=sz[u];
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

 

posted @ 2017-09-26 19:43  友人Aqwq  阅读(206)  评论(0编辑  收藏  举报