【GDOI2017模拟二试4.12】树上路径(点剖)

Description

给定一颗n个结点的无根树,树上的每个点有一个非负整数点权,定义一条路径的价值为路径上的点权和-路径的点权最大值。
给定参数p,我们想知道,有多少不同的树上简单路径,满足它的价值恰好是p的倍数。
注意:单点算作一个路径;u ≠ v时,(u,v)和(v,u)只算一次。

Solution

本来是一道很裸的点剖题,但是很久没有打点剖了,并没有去打。
点剖的时候,每个点维护两个值,一个是最大值,还有一个是和。
然后以最大值为第一关键字排序(因为要使得这个最大值一定最大才好统计方案),然后用一个桶记录一下就好了。

Code

#include
#include
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define rep(i,a) for(i=first[a];i;i=next[i])
using namespace std;
const int maxn=1e5+7,mxx=1e7+7;
int i,j,k,l,t,n,m,ans,p;
int first[maxn*2],last[maxn*2],next[maxn*2],num;
int a[maxn],z,dian,tot,c[mxx][2],tot1,size[maxn],g[maxn],tim;
bool bz[maxn];
struct node{
    int da,sum;
}b[maxn],d[maxn];
bool cmp(node x,node y){return x.da<y.da;}
void add(int x,int y){last[++num]=y,next[num]=first[x],first[x]=num;}
void dfs1(int x,int y){
    int i;
    size[x]=1;
    rep(i,x)if(!bz[last[i]]&&last[i]!=y)dfs1(last[i],x),size[x]+=size[last[i]];
}
void dfs2(int x,int y){
    int i;bool az=1;
    rep(i,x)if(!bz[last[i]]&&last[i]!=y){
        if(size[last[i]]>dian/2)az=0;
        dfs2(last[i],x);
    }
    if(az&&dian-size[x]<=dian/2)z=x;
}
void dfs3(int x,int y,int da,int sum){
    int i;
    b[++tot]=(node){da,sum};
    rep(i,x)if(last[i]!=y&&!bz[last[i]])dfs3(last[i],x,max(da,g[last[i]]),(sum+a[last[i]])%p);
}
void fen(int x){
    int i;
    dfs1(x,0);dian=size[x];dfs2(x,0);x=z;
    tot1=0;
    bz[x]=1;
    d[tot=1]=(node){g[x],a[x]};
    rep(i,x){
        if(bz[last[i]])continue;
        tot=0;
        dfs3(last[i],x,max(g[last[i]],g[x]),(a[x]+a[last[i]])%p);
        fo(j,1,tot)d[++tot1]=b[j];
        sort(b+1,b+1+tot,cmp);
        ++tim;
        fo(j,1,tot){
            k=((b[j].sum-b[j].da)%p+p)%p;
            if(k)k=p-k;
            if(c[k][0]!=tim)c[k][0]=tim,c[k][1]=0;
            ans-=c[k][1];
            k=((b[j].sum-a[x])%p+p)%p;
            if(c[k][0]!=tim)c[k][0]=tim,c[k][1]=0;
            c[k][1]++;
        }
    }
    sort(d+1,d+1+tot1,cmp);
    ++tim;
    fo(i,1,tot1){
        k=((d[i].sum-d[i].da)%p+p)%p;
        if(!k)ans++;else k=p-k;
        if(c[k][0]!=tim)c[k][0]=tim,c[k][1]=0;
        ans+=c[k][1];
        k=((d[i].sum-a[x])%p+p)%p;
        if(c[k][0]!=tim)c[k][0]=tim,c[k][1]=0;
        c[k][1]++;
    }
    rep(i,x){
        if(!bz[last[i]])fen(last[i]);
    }
}
int main(){
    freopen("path.in","r",stdin);
    freopen("path.out","w",stdout);
    scanf("%d%d",&n,&p);
    fo(i,1,n-1){
        scanf("%d%d",&k,&l);
        add(k,l),add(l,k);
    }
    fo(i,1,n)scanf("%d",&a[i]),g[i]=a[i],a[i]%=p;
    fen(1);
    printf("%d\n",ans+n);
}

你可能感兴趣的:(省选,树分治)