zoukankan      html  css  js  c++  java
  • 青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)

    题目链接

    1.对于简单的版本n<=500, ai<=50

    直接暴力枚举两个点x,y,dfs求x与y的距离。

    2.对于普通难度n<=10000,ai<=500

    普通难度解法挺多

    第一种,树形dp+LCA

    比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2

    然后找出所有互质的对数,然后对为1的数进行特殊处理。(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7)

    对所有的非1质数对,采用离线LCA可以搞定。

    对于1的特殊情况,只需要用两次dfs,就可以找出所有1到其它点的距离和与1之间的距离和。

    第二种,树形dp+容斥

    这种方法从边的角度,考虑每一条边会被计算多少次,这也是树上求距离的常用方法。

    由于树边都是桥边,所有只要求出边两边联通块之间互质对数。最简单的想法即是枚举每一条边,然后再分别枚举两边区域,这样的复杂度是500*500*10000 很遗憾并没有这么简单。于是用容斥原理进行优化。在枚举某条边的一边的x(1<=x<=500)的时候,考虑右边为x质因子倍数的情况,也就是容斥了。 这样可以将复杂度变为10000*500*k*2^k( k<=4)

    官方题解:

     附上代码:

    //
    //  main.cpp
    //  160701
    //
    //  Created by New_Life on 16/7/1.
    //  Copyright © 2016年 chenhuan001. All rights reserved.
    //
    
    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <vector>
    #include <algorithm>
    using namespace std;
    #define N 10100
    
    vector<int> save[505];
    int g[N];
    struct node
    {
        int to,next;
    }edge[2*N];
    
    int cnt,pre[N];
    int dp[N][505];
    int savecnt[N][505];
    int cntall[505];
    long long ans;
    
    void add_edge(int u,int v)
    {
        edge[cnt].to = v;
        edge[cnt].next = pre[u];
        pre[u] = cnt++;
    }
    
    void dfs(int s,int fa)
    {
        for(int p=pre[s];p!=-1;p=edge[p].next)
        {
            int v = edge[p].to;
            if(v == fa) continue;
            dfs(v,s);
            for(int i=1;i<=500;i++)
            {
                dp[s][i] += dp[v][i];
                savecnt[s][i] += savecnt[v][i];
            }
        }
        
        savecnt[s][ g[s] ]++;
    
        for(int i=0;i<(1<<save[g[s]].size());i++)
        {
            int tmp = 1;
            for(int j=0;j<save[g[s]].size();j++)
            {
                if(((1<<j)&i) != 0)
                {
                    tmp *= save[g[s]][j];
                }
            }
            dp[s][tmp]++;
        }
        
        //int last[505];
        int lastcnt[505];
        for(int p=pre[s];p!=-1;p=edge[p].next)
        {
            int v = edge[p].to;
            if(v == fa) continue;
            for(int i=1;i<=500;i++)
            {
                //last[i] = all[i]-dp[v][i];
                lastcnt[i] = cntall[i]-savecnt[v][i];
            }
            //对这条边进行处理
            for(int i=1;i<=500;i++)
            {
                if(lastcnt[i] == 0) continue;
                for(int j=0;j<(1<<save[i].size());j++)
                {
                    int tcnt=0;
                    int tnum = 1;
                    for(int k=0;k<save[i].size();k++)
                    {
                        if( ((1<<k)&j)!=0 )
                        {
                            tcnt++;
                            tnum *= save[i][k];
                        }
                    }
                    if(tcnt%2 == 0) ans += lastcnt[i]*dp[v][tnum];
                    else ans -= lastcnt[i]*dp[v][tnum];
                }
            }
        }
        
    }
    
    int main(int argc, const char * argv[]) {
        for(int i=1;i<=500;i++)
        {
            int ti = i;
            for(int j=2;j*j<=ti;j++)
            {
                if(ti%j == 0)
                {
                    save[i].push_back(j);
                    while(ti%j==0) ti/=j;
                }
            }
            if(ti != 1) save[i].push_back(ti);
        }
        //for(int i=1;i<=500;i++) printf("%d
    ",save[i].size());
        cnt = 0;
        memset(pre,-1,sizeof(pre));
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",g+i);//得把每一项都变成最简单
            int tg =1;
            for(int j=0;j<save[ g[i] ].size();j++)
            {
                tg *= save[ g[i] ][j];
            }
            g[i] = tg;
        }
        for(int i=1;i<n;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        ans = 0;
        for(int ii=1;ii<=n;ii++)
        {
            cntall[ g[ii] ]++;
        }
        
        dfs(1,-1);
        cout<<ans<<endl;
        return 0;
    }

    3. 对于困难难度

    这就需要用到虚树这种没听过的东西了,百度学习下,然后发现原理还是很简单的。应用情景,对于一颗树,挺大,但是需要操作的结点不多,这时候把需要操作的结点重新建一颗小的树(需要用的信息不能少)。 

    思路概述:(抄了下)

    1. 枚举 因数x,x是每种质因子至多有一个的数,记录一下x有几种质因子,方便之后容斥。
    2. 把所有x的倍数的权值的点找出来,预处理下可以做到找出来的点的dfs序是从小到大的,预处理也可以使得每次找x的倍数的权值的点不必线性扫一遍。
    3. 然后对这些点 O(n) 建虚树,具体操作是相邻两个点加进去 lca,用一个栈维护下父亲链即可。[bzoj3572]是一道典型的虚树的题目。
    4. 构建好树后在树上 dfs 两次可以求出所有x的倍数的权值的点对之间的距离和,就是第一遍dfs记录以节点u为根的子树中,有多少个x倍数的点(可能有一些是虚树添加进来的lca点),第二遍dfs其实是枚举每条边,计算(u,v)这条边的总价值,就是它出现的次数乘以它的权值;它出现的次数就是它子树中x倍数的点的个数,乘以不在它子树中x倍数的点的个数。
    5. 最后容斥下就可以求出答案。

    由于所有步骤均是线性的,而所有虚树加起来的总点数也是线性乘上一个常数的,所以复杂度为 O(nK),K<=128。

    对于复杂度分析,我抱有不同的看法,上述过程中建虚树是O(nlog(n))的,100000以内不重复质数最多是6个,所以最大复杂度为O(64*n*log(n))

    //
    //  main.cpp
    //  Xushu
    //
    //  Created by New_Life on 16/7/1.
    //  Copyright © 2016年 chenhuan001. All rights reserved.
    //
    
    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    #define N 100100
    #define LN 20
    
    struct node
    {
        int to,next;
    }edge[2*N];
    
    int cnt,pre[N];
    
    void add_edge(int u,int v)
    {
        edge[cnt].to = v;
        edge[cnt].next = pre[u];
        pre[u] = cnt++;
    }
    
    int deep[N];
    int g[N];//记录每个点的权值
    vector<int>saveall[N];//记录i所有的倍数
    int sign[N];
    int len[N];//每个点到根的距离
    int mark[N];//标示虚树上的点是否是无用点
    
    struct Lca_Online
    {
        int _n;
        
        int dp[N][LN];
        
        void _dfs(int s,int fa,int dd)
        {
            int factor[30];
            int fcnt=0;
            int tmp = g[s];
            for(int i=2;i*i<=tmp;i++)
            {
                if(tmp%i == 0)
                {
                    factor[ fcnt++ ] = i;
                    while(tmp%i == 0) tmp/=i;
                }
            }
            if(tmp != 1) factor[ fcnt++ ] = tmp;
            
            for(int i=0;i<(1<<fcnt);i++)
            {
                tmp = 1;
                int tsign = 1;
                for(int j=0;j<fcnt;j++)
                    if( ((1<<j)&i) != 0 )
                    {
                        tmp *= factor[j];
                        tsign *= -1;
                    }
                saveall[tmp].push_back(s);
                sign[tmp] = tsign;
                
            }
            
            deep[s] = dd;
            for(int p=pre[s];p!=-1;p=edge[p].next)
            {
                int v = edge[p].to;
                if(v == fa) continue;
                _dfs(v,s,dd+1);
                dp[v][0] = s;
            }
        }
        
        void _init()
        {
            for(int j=1;(1<<j)<=_n;j++)
            {
                for(int i=1;i<=_n;i++)
                {
                    if(dp[i][j-1]!=-1) dp[i][j] = dp[ dp[i][j-1] ][j-1];
                }
            }
        }
        void lca_init(int n)
        {
            _n = n;
            memset(dp,-1,sizeof(dp));
            //_dfs(firstid,-1,0);
            _dfs(1,-1,0);
            _init();
        }
        
        int lca_query(int a,int b)
        {
            if(deep[a]>deep[b]) swap(a,b);
            //调整b到a的同一高度
            for(int i=LN-1;deep[b]>deep[a];i--)
                if(deep[b]-(1<<i) >= deep[a]) b = dp[b][i];
            if(a == b) return a;
            for(int i=LN-1;i>=0;i--)
            {
                if(dp[a][i]!=dp[b][i]) a = dp[a][i],b = dp[b][i];
            }
            return dp[a][0];
        }
    }lca;
    
    int stk[N],top;
    vector<int>tree[N];//存边
    vector<int>treew[N];//存权
    
    void tree_add(int u,int v,int w)
    {
        tree[u].push_back(v);
        tree[v].push_back(u);
        treew[u].push_back(w);
        treew[v].push_back(w);
    }
    
    long long down[N];
    long long ccnt[N];
    long long sum[N];
    int nn;
    
    void dfs1(int s,int fa)
    {
        down[s] = 0;
        ccnt[s] = 0;
        for(int i=0;i<tree[s].size();i++)
        {
            int to = tree[s][i];
            if(to == fa) continue;
            dfs1(to,s);
            down[s] += down[to] + ccnt[to]*treew[s][i];
            ccnt[s] += ccnt[to];
        }
        if(mark[s]==1)
            ccnt[s]++;
    }
    
    void dfs2(int s,int fa,long long num,long long tcnt)
    {
        sum[s] = down[s]+num+tcnt;
        for(int i=0;i<tree[s].size();i++)
        {
            int to = tree[s][i];
            if(to == fa) continue;
            dfs2(to,s,sum[s]-down[to]-ccnt[to]*treew[s][i],(nn-ccnt[to])*treew[s][i]);
        }
    }
    
    int main(int argc, const char * argv[]) {
        cnt = 0;
        memset(pre,-1,sizeof(pre));
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",g+i);
        }
        for(int i=1;i<n;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a, b);
            add_edge(b, a);
        }
        
        lca.lca_init(n);
        long long ans=0;
        
        for(int x=1;x<=100000;x++)
        {
            if(saveall[x].size() == 0) continue;
            //build virtual tree
            top = 0;
            
            stk[top++] = saveall[x][0];
            tree[ saveall[x][0] ].clear();
            treew[ saveall[x][0] ].clear();
            mark[saveall[x][0]]=1;
            for(int i=1;i<saveall[x].size();i++)
            {
                int v = saveall[x][i];
                
                int plca = lca.lca_query(stk[top-1], v);//最近公共祖先
                if(plca == stk[top-1]) ;//不处理
                else
                {
                    
                    int pos=top-1;
                    while(pos>=0 && deep[ stk[pos] ]>deep[plca])
                        pos--;
                    pos++;
                    for(int j=pos;j<top-1;j++)
                    {
                        tree_add(stk[j],stk[j+1],deep[stk[j+1]]-deep[stk[j]]);
                    }
                    int prepos = stk[pos];
                    if(pos == 0)
                    {
                        tree[plca].clear(),treew[plca].clear(),stk[0]=plca,top=1;
                        mark[plca] = 0;
                    }
                    else if(stk[pos-1] != plca)
                    {
                        tree[plca].clear(),treew[plca].clear(),stk[pos]=plca,top=pos+1;
                        mark[plca] = 0;
                    }
                    else top = pos;
                    tree_add(prepos,plca,deep[prepos]-deep[plca]);
                    
                }
                tree[v].clear();
                treew[v].clear();
                stk[top++] = v;
                mark[v] = 1;
            }
            for(int i=0;i<top-1;i++)
            {
                tree_add(stk[i], stk[i+1], deep[stk[i+1]]-deep[stk[i]]);
            }
            //构建好了虚树,然后就是两次dfs
            nn = (int)saveall[x].size();
            dfs1(saveall[x][0],-1);
            dfs2(saveall[x][0],-1,0,0);
            long long tans=0;
            for(int i=0;i<saveall[x].size();i++)
                tans += sum[ saveall[x][i] ];
            tans /= 2;
            
            ans += sign[x]*tans;
        }
         
        cout<<ans<<endl;//时间,内存。
        
        return 0;
    }
  • 相关阅读:
    rs
    stm32f767 usoc3
    stm32f767 RTT 日志
    stm32f767 标准库 工程模板
    stm32f767 HAL 工程模板
    docker tab 补全 linux tab 补全
    docker anconda 依赖 下载 不了
    docker run 常用 指令
    linux scp 命令
    Dockerfile 常用参数说明
  • 原文地址:https://www.cnblogs.com/chenhuan001/p/5638841.html
Copyright © 2011-2022 走看看