zoukankan      html  css  js  c++  java
  • GalaxyOJ-510 (点分治)

    题目

    Description
    给一棵 n 个节点的无根树,每个节点有个非负整点权,定义一条路径的价值为路径上的(点权和)-(点权最大值)
    给定参数 p ,求有多少条不同的简单路径满足它的价值恰好为 p 的倍数。
    注意,单点也算做一个路径,u!=v 时, u->vv->u 算作一条路径。
    简单路径就是不经过重复点的一条路径。

    [nleqslant 10^5 \ pleqslant 10^7 \ v[i] leqslant 10^9 ]

    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,导致答案错误了。
    • 可以先看看我打的第一道点分治题(模板题): 传送门
    • 大家加油呀~
  • 相关阅读:
    js,vue.js一些方法的总结
    confirm提示弹出确定和取消按钮
    移动端 meta 必备
    Vue.js总结 [2017.6.5]
    2017.6.5项目总结(移动端touch事件)
    微信公众平台接口开发(全面认识接口)
    数据库作业
    数据库子函数等
    判断一年是否为闰年
    数据库练习
  • 原文地址:https://www.cnblogs.com/hehepig/p/6730428.html
Copyright © 2011-2022 走看看