洛谷P4178 Tree

考虑点分治,对于一个子树,求出每个点到根的距离并排序,用双指针扫描,但这样会把来自同一个儿子的路径也统计上去,再对每个儿子做一遍,减去即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=40005;
char rB[1<<21],*S,*T;
inline char gc(){return S==T&&(T=(S=rB)+fread(rB,1,1<<21,stdin),S==T)?EOF:*S++;}
inline int rd(){
    char c=gc();
    while(c<48||c>57)c=gc();
    int x=c&15;
    for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15);
    return x;
}
int G[N],to[N<<1],w[N<<1],nxt[N<<1],cnt=0,sz[N],f[N],sum,rt,d[N],t[N],tot,k;
ll ans=0ll;
bool vis[N],inq[N];
queue<int> Q;
inline void add(int u,int v,int c){
    to[++cnt]=v;w[cnt]=c;nxt[cnt]=G[u];G[u]=cnt;
    to[++cnt]=u;w[cnt]=c;nxt[cnt]=G[v];G[v]=cnt;
}
void getrt(int u,int fa){  //点分治搜重心
    int i,v;
    sz[u]=1;f[u]=0;
    for(i=G[u];i;i=nxt[i])if(!vis[v=to[i]]&&v!=fa){
        getrt(v,u);
        sz[u]+=sz[v];
        f[u]=max(f[u],sz[v]);
    }
    f[u]=max(f[u],sum-sz[u]);
    if(f[u]<f[rt])rt=u;
}
inline ll calc(int u,int k){
    int i,h,v,l,r;
    ll ans=0ll;
    memset(inq,0,sizeof(inq));
    inq[u]=1;t[tot=1]=d[u]=0;Q.push(u);
    while(!Q.empty()){
        h=Q.front();Q.pop();
        for(i=G[h];i;i=nxt[i])if(!vis[v=to[i]]&&!inq[v]&&(d[v]=d[h]+w[i])<=k){  //大于k的距离对答案无贡献,不用保存
            t[++tot]=d[v];
            inq[v]=1;
            Q.push(v);
        }
    }
    sort(t+1,t+tot+1);
    for(l=1,r=tot;l<=r;++l){
        while(l<=r&&t[l]+t[r]>k)--r;
        if(l>r)break;
        ans+=r-l;
    }
    return ans;
}
void solve(int u){
    int i,v;
    vis[u]=1;ans+=calc(u,k);
    for(i=G[u];i;i=nxt[i])if(!vis[v=to[i]]){
        ans-=calc(v,k-(w[i]<<1));
        sum=sz[v];rt=0;getrt(v,u);
        solve(rt);
    }
}
int main(){
    int n=f[0]=sum=rd(),i,u,v,c;
    for(i=1;i<n;++i){
        u=rd();v=rd();c=rd();
        add(u,v,c);
    }
    k=rd();
    getrt(1,0);solve(rt);
    printf("%lld",ans);
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/sunshine-chen/p/11258752.html
今日推荐