GalaxyOJ-510 (点分治)

来源:互联网 发布:流程优化岗面试题 编辑:程序博客网 时间:2024/04/29 18:59

题目

链接 https://vijos.org/d/hehepig/p/58f5d7f8d3d8a10adc823e6e

Description

给一棵 n 个节点的无根树,每个节点有个非负整点权,定义一条路径的价值为路径上的(点权和)-(点权最大值)

给定参数 p ,求有多少条不同的简单路径满足它的价值恰好为 p 的倍数。

注意,单点也算做一个路径,u!=v 时, u->vv->u 算作一条路径。

简单路径就是不经过重复点的一条路径。

n105p107v[i]109

Input

第一行包含两个数 n,p;
接下来 n-1 行,每行两个数表示那两个点之间有一条树边;
接下来一行 n 个整数 (v[1]~v[n]),表示点 i 的权值.

Outpug

就一个数,表示答案

Sample Input

5 2
1 2
1 3
2 4
3 5
1 3 3 1 2

Sample Output

9

Hint

满足条件的路径有:
(1,1),(2,2),(3,3),(4,4),(5,5),(1,4),(2,3),(2,5),(3,5)

分析

  • 数据庞大,所以考虑点分治
  • 基本过程和模板题差不多,不过关于减去最大点权这一点,我们应该稍作处理。
  • 可以每次求 dis[] 时,把 mx[] 也顺便求出来(这条路径中最大点权),之后以 mx[] 排个序,之后就能 O(n) 处理出当前子树的答案了。(具体可以看一看程序中的一小段注释)
  • 注意,求 dis[] 的时候就应该先 %p
  • 还有一点,单点也算是一条路径,而显然单点路径的价值为0,那么肯定是可以有贡献的,于是最后输出 ans+n

程序

#include <cstdio>#include <algorithm>#define Mn 100005#define Mv 10000005#define add(x,y) (to[++num]=head[x],head[x]=num,edge[num]=y)#define For(x) for (int h=head[x],o=edge[h]; h; o=edge[h=to[h]])using namespace std;int n,p,ans;int edge[2*Mn+5],to[2*Mn+5],head[2*Mn+5],del[Mn+5],num;int R,cnt,mins,v[Mn+5],dis[Mn+5],siz[Mn+5],mx[Mn+5],com[Mn+5],bin[Mv+5],daan;/*部分变量说明 del[i]      节点 i 还在不在(每次分治完都把重心去掉了,免得又dfs回到上面) dis[]       从重心到子树中各个节点的路径价值的集合 mx[]        dis[]中各个路径对应的最大点权值 bin[i]      一个容器,存储到当前 dis[] 为 i 的路径个数(具体用法可以看看程序) com[]       辅助排序(看看cmp应该就能懂了) siz[i]      当前重心下以 x 为根节点的子树的大小 */int mo(int x){    int kkk=x%p;    return (kkk<0 ? kkk+p:kkk);}void Input(){    scanf("%d%d",&n,&p);    for (int i=1,o1,o2; i<n; i++) scanf("%d%d",&o1,&o2),add(o1,o2),add(o2,o1);    for (int i=0; i<n;) scanf("%d",&v[++i]);    return;}int get_siz(int x,int fa){    siz[x]=1;    For (x) if (o!=fa && !del[o]) siz[x]+=get_siz(o,x);    return siz[x];}void dfs_R(int x,int tot,int fa){   //求重心(比较 x 变为中心的话分出的子树中大小最大值)     int mxs=tot-siz[x];    For(x) if (o!=fa && !del[o]){        mxs=max(mxs,siz[o]);        dfs_R(o,tot,x);    }    if (mxs<mins) mins=mxs,R=x;}void get_dis(int x,int tot,int lmx,int fa){ //求子树中每个点到重心这条路径的价值     dis[++cnt]=tot%=p;//-----------------!!!!!就是这里我忘记 %,调了一中午 T^T      mx[cnt]=lmx;    For(x) if (!del[o] && o!=fa)        get_dis(o,tot+v[o],max(lmx,v[o]),x);}bool cmp(int x,int y){return mx[x]<mx[y];}int work(int x,int r){    //处理当前中心下两端点都在以 x 为根节点的子树中经过重心的符合路径个数(先不管是不是"简单路径")    cnt=daan=0;    if (x==r) get_dis(x,v[x],v[x],0);    else get_dis(x,v[x]+v[r],max(v[x],v[r]),r);    for (int i=1; i<=cnt; i++) com[i]=i;    sort(com+1,com+cnt+1,cmp);    for (int i=1,I=com[i]; i<=cnt; I=com[++i]){        //dis[I]+dis[j]-v[r]-mx[I] == 0 (%p)        //dis[j] == v[r]+mx[I]-dis[I] (%p)        //大概意思就是一个端点为 I ,另一个端点为 mx[] 小于等于 mx[I](即已经加入bin[]中的端点)         daan+=bin[mo(mx[I]+v[r]-dis[I])];        bin[dis[I]]++;    }    for (int i=1; i<=cnt; i++) bin[dis[i]]=0;    return daan;}void dfs(int x){    //分治总过程     cnt=0;    mins=Mn+1;    get_siz(x,0);    dfs_R(x,siz[x],0);    int rr=R;    ans+=work(rr,rr);    del[rr]=1;    For(rr) if (!del[o]){        ans-=work(o,rr);        dfs(o);    }}int main(){    Input();    dfs(1);    printf("%lld",ans+n);}

提示

  • 这是我做的第二道点分题,还是有点不熟练,起先一直 RE,调了一中午才发现原来是在 get_dis() 那里我的 tot 并没有%,于是数据一大,tot 就会溢出 int,导致答案错误了。
  • 可以先看看我打的第一道点分治题(模板题): 传送门
  • 大家加油呀~
1 0
原创粉丝点击