gdfzoj #510 树上路径(点分治)

第一次打点分治,看了许多代码、用了很久才打出来。。。
标签:点分治
原题链接:http://www.gdfzoj.com/oj/contest/115/problems/4 (其实你没帐号也进不去)
(文字复制不了,只有图片了)题意♂如下
这里写图片描述
这里写图片描述
由于数据范围比较大,所以考虑用点分治。点分治的总体过程就是先求出一个重心(就是去掉重心后,剩下的最大的子树最小)。然后分成子树继续进行上述过程,时间复杂度就能降到 O(nlogn) 。这只是一个算法框架,具体的看代码。
当然还有一些需要注意的细节:每个点到重心的路径的价值的时候要记得mod p,不然会爆掉;另外,最后的答案要加上n,因为单点也算是一条路径,而该路径的价值显然是0。代码中还有一些注释。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxp 10000500
#define maxn 200050
#define add(u,v) (to[++top]=head[u],head[u]=top,w[top]=v)
#define For(x) for (int h=head[x],o=w[h];h;o=w[h=to[h]]) //邻接表存树边 
using namespace std;
int to[maxn],head[maxn],w[maxn],k[maxn],e[maxn],size[maxn],com[maxn],bin[maxp],mx[maxn],dis[maxn],n,p,i,u,v,ans,cnt,mins,root,now,top;
int m(int x)
{
    int kk=x%p;
    if (kk>=0) return kk;else return kk+p;
}
int dfss(int x,int fa) //求以每个点为树根的子树大小 
{
    size[x]=1; 
    For(x) if (e[o]&&o!=fa) size[x]+=dfss(o,x);
    return size[x];
}
bool cmp(int x,int y)
{
    return mx[x]<mx[y];
}
void get(int x,int tot,int l,int fa) //求每个点到重心的路径的价值 
{
    dis[++cnt]=tot%=p; mx[cnt]=l;
    For(x) if (e[o]&&o!=fa) get(o,tot+k[o],max(k[o],l),x);
}
int work(int x,int r) //处理当前子树以x为树根的符合要求的路径个数 
{
    now=cnt=0; int ii;
    if (x==r) get(x,k[x],k[x],0);else get(x,k[x]+k[r],max(k[x],k[r]),r);
    for (i=1;i<=cnt;i++) com[i]=i; sort(com,com+cnt+1,cmp);
    for (i=1,ii=com[i];i<=cnt;ii=com[++i])
        now+=bin[m(mx[ii]+k[r]-dis[ii])],bin[dis[ii]]++;
    for (i=1;i<=cnt;i++) bin[dis[i]]=0;
    return now;
}
void dfsr(int x,int tot,int fa) //求重心 
{
    int c=tot-size[x];
    For(x) if (e[o]&&o!=fa)
    {
        c=max(c,size[o]); dfsr(o,tot,x);
    }
    if (c<mins) mins=c,root=x;
}
void dfs(int x) //分治过程 
{
    cnt=0; mins=maxn+1; dfss(x,0);
    dfsr(x,size[x],0); int rr=root;
    ans+=work(rr,rr); e[rr]=0;
    For(rr) if (e[o])
    {
        ans-=work(o,rr),dfs(o);
    }
}
int main()
{
    scanf("%d%d",&n,&p); 
    top=0;
    for (i=1;i<n;i++) 
    {
    scanf("%d%d",&u,&v);
    add(u,v); add(v,u);
    }
    for (i=1;i<=n;i++) scanf("%d",&k[i]),e[i]=1;
    dfs(1);
    printf("%d",ans+n);
    return 0;
}

最后安利一道具有挑战性的题:紫荆花之恋http://uoj.ac/problem/55(动态点分治)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值