• 青云的机房组网方案(简单+普通+困难)(虚树+树形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;
    }
  • 相关阅读:
    最短路小变形
    最短路
    bfs + 路径输出
    优先队列
    dijkstra
    重载运算符-operator
    最短路
    二分图匹配
    依赖型的关系建立
    string 类
  • 原文地址:https://www.cnblogs.com/chenhuan001/p/5638841.html
Copyright © 2020-2023  润新知