[BZOJ3697]采药人的路径(点分治)

=== ===

这里放传送门

=== ===

题解

首先考虑如果没有那个休息站的限制的话应该怎么做,可以想到把边权为0的变成边权为-1,这样的话就是找有多少条边权和为0的路径,这个东西非常好办。但是如果加入休息站的话要求这段路径的某一段前缀边权也为0。
考虑如果存在休息站的话这条路径有什么特点,只考虑经过当前子树根节点的路径,如果休息站恰好在根节点上,那么这条路径一定是两条deep为0的路径拼起来的;否则在这条路径的左侧或者右侧一定存在一个点,它的deep值和端点的deep值相等。
那么可以在对子树dfs的时候开一个数组表示这个deep值出现过没有。如果一条路径上已经出现了相等的deep,那么它可以跟任意路径拼成合法路径;但是如果是没有出现过相等deep的路径,那么只能是两条权值和为0的路径拼在一起。所以两种路径要分开存储分别计算。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define base 100000
#define inf 1000000000
using namespace std;
int n,p[100010],a[200010],nxt[200010],w[200010],deep[100010],size[100010],d[100010],c2;
int son[100010],ext[200010],root,cnt,tot;
long long ans;
bool vis[100010];
void add(int x,int y,int val){
    tot++;a[tot]=y;nxt[tot]=p[x];w[tot]=val;p[x]=tot;
}
void getroot(int u,int fa,int N){
    size[u]=1;son[u]=0;
    for (int i=p[u];i!=0;i=nxt[i])
      if (a[i]!=fa&&vis[a[i]]==false){
          getroot(a[i],u,N);
          size[u]+=size[a[i]];
          son[u]=max(son[u],size[a[i]]);
      }
    son[u]=max(son[u],N-size[u]);
    if (son[u]<son[root]) root=u;
}
void getdeep(int u,int fa,int dep,int dlt){
    if (ext[dep+base]!=0)
      deep[++cnt]=dep;
    else if (ext[base]!=0) d[++c2]=dep;//根节点的空路径不能加进去
    if (dep==0&&ext[dep+base]>=2) ans+=(long long)dlt;
    ext[dep+base]++;
    for (int i=p[u];i!=0;i=nxt[i])
      if (a[i]!=fa&&vis[a[i]]==false)
        getdeep(a[i],u,dep+w[i],dlt);
    ext[dep+base]--;
}
long long calc(int u,int d0,int dlt){
    long long sum=0;
    cnt=c2=0;getdeep(u,0,d0,dlt);
    sort(deep+1,deep+cnt+1);
    sort(d+1,d+c2+1);
    deep[0]=d[0]=-inf;
    for (int l=1,r=cnt,p=r;l<r;){
        while (p>l&&deep[p]>=deep[r]) --p;
        p=max(p,l);//用一个指针找到权值相等的部分,注意p不能小于l。
        if (deep[l]+deep[r]==0){
          sum+=(long long)(r-p);++l;
        }else 
          if (deep[l]+deep[r]>0) --r;
          else ++l;
    }//计算存在相等前缀的路径
    for (int l=1,r=cnt,p=r;l<=c2&&r>=1;){
        while (p>=1&&deep[p]>=deep[r]) --p;
        if (d[l]+deep[r]==0){
            sum+=(long long)(r-p);++l;
        }else
          if (d[l]+deep[r]>0) --r;
          else ++l;
    }//计算没有相等前缀的路径
    return sum;
}
void work(int u){
    ans+=calc(u,0,1);
    vis[u]=true;
    for (int i=p[u];i!=0;i=nxt[i])
      if (vis[a[i]]==false){
          ext[base]++;
          ans-=calc(a[i],w[i],-1);
          ext[base]--;
          root=0;getroot(a[i],u,size[a[i]]);
          work(root);
      }
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<n;i++){
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        v=(v==0)?-1:1;
        add(x,y,v);add(y,x,v);
    }
    root=0;son[0]=inf;
    getroot(1,0,n);
    work(root);
    printf("%I64d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值