青云的机房组网方案(简单+普通+困难)(虚树+树形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[];
- int g[N];
- struct node
- {
- int to,next;
- }edge[*N];
- int cnt,pre[N];
- int dp[N][];
- int savecnt[N][];
- int cntall[];
- 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!=-;p=edge[p].next)
- {
- int v = edge[p].to;
- if(v == fa) continue;
- dfs(v,s);
- for(int i=;i<=;i++)
- {
- dp[s][i] += dp[v][i];
- savecnt[s][i] += savecnt[v][i];
- }
- }
- savecnt[s][ g[s] ]++;
- for(int i=;i<(<<save[g[s]].size());i++)
- {
- int tmp = ;
- for(int j=;j<save[g[s]].size();j++)
- {
- if(((<<j)&i) != )
- {
- tmp *= save[g[s]][j];
- }
- }
- dp[s][tmp]++;
- }
- //int last[505];
- int lastcnt[];
- for(int p=pre[s];p!=-;p=edge[p].next)
- {
- int v = edge[p].to;
- if(v == fa) continue;
- for(int i=;i<=;i++)
- {
- //last[i] = all[i]-dp[v][i];
- lastcnt[i] = cntall[i]-savecnt[v][i];
- }
- //对这条边进行处理
- for(int i=;i<=;i++)
- {
- if(lastcnt[i] == ) continue;
- for(int j=;j<(<<save[i].size());j++)
- {
- int tcnt=;
- int tnum = ;
- for(int k=;k<save[i].size();k++)
- {
- if( ((<<k)&j)!= )
- {
- tcnt++;
- tnum *= save[i][k];
- }
- }
- if(tcnt% == ) ans += lastcnt[i]*dp[v][tnum];
- else ans -= lastcnt[i]*dp[v][tnum];
- }
- }
- }
- }
- int main(int argc, const char * argv[]) {
- for(int i=;i<=;i++)
- {
- int ti = i;
- for(int j=;j*j<=ti;j++)
- {
- if(ti%j == )
- {
- save[i].push_back(j);
- while(ti%j==) ti/=j;
- }
- }
- if(ti != ) save[i].push_back(ti);
- }
- //for(int i=1;i<=500;i++) printf("%d\n",save[i].size());
- cnt = ;
- memset(pre,-,sizeof(pre));
- int n;
- scanf("%d",&n);
- for(int i=;i<=n;i++)
- {
- scanf("%d",g+i);//得把每一项都变成最简单
- int tg =;
- for(int j=;j<save[ g[i] ].size();j++)
- {
- tg *= save[ g[i] ][j];
- }
- g[i] = tg;
- }
- for(int i=;i<n;i++)
- {
- int a,b;
- scanf("%d%d",&a,&b);
- add_edge(a,b);
- add_edge(b,a);
- }
- ans = ;
- for(int ii=;ii<=n;ii++)
- {
- cntall[ g[ii] ]++;
- }
- dfs(,-);
- cout<<ans<<endl;
- return ;
- }
3. 对于困难难度
这就需要用到虚树这种没听过的东西了,百度学习下,然后发现原理还是很简单的。应用情景,对于一颗树,挺大,但是需要操作的结点不多,这时候把需要操作的结点重新建一颗小的树(需要用的信息不能少)。
思路概述:(抄了下)
- 枚举 因数x,x是每种质因子至多有一个的数,记录一下x有几种质因子,方便之后容斥。
- 把所有x的倍数的权值的点找出来,预处理下可以做到找出来的点的dfs序是从小到大的,预处理也可以使得每次找x的倍数的权值的点不必线性扫一遍。
- 然后对这些点 O(n) 建虚树,具体操作是相邻两个点加进去 lca,用一个栈维护下父亲链即可。[bzoj3572]是一道典型的虚树的题目。
- 构建好树后在树上 dfs 两次可以求出所有x的倍数的权值的点对之间的距离和,就是第一遍dfs记录以节点u为根的子树中,有多少个x倍数的点(可能有一些是虚树添加进来的lca点),第二遍dfs其实是枚举每条边,计算(u,v)这条边的总价值,就是它出现的次数乘以它的权值;它出现的次数就是它子树中x倍数的点的个数,乘以不在它子树中x倍数的点的个数。
- 最后容斥下就可以求出答案。
由于所有步骤均是线性的,而所有虚树加起来的总点数也是线性乘上一个常数的,所以复杂度为 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[*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[];
- int fcnt=;
- int tmp = g[s];
- for(int i=;i*i<=tmp;i++)
- {
- if(tmp%i == )
- {
- factor[ fcnt++ ] = i;
- while(tmp%i == ) tmp/=i;
- }
- }
- if(tmp != ) factor[ fcnt++ ] = tmp;
- for(int i=;i<(<<fcnt);i++)
- {
- tmp = ;
- int tsign = ;
- for(int j=;j<fcnt;j++)
- if( ((<<j)&i) != )
- {
- tmp *= factor[j];
- tsign *= -;
- }
- saveall[tmp].push_back(s);
- sign[tmp] = tsign;
- }
- deep[s] = dd;
- for(int p=pre[s];p!=-;p=edge[p].next)
- {
- int v = edge[p].to;
- if(v == fa) continue;
- _dfs(v,s,dd+);
- dp[v][] = s;
- }
- }
- void _init()
- {
- for(int j=;(<<j)<=_n;j++)
- {
- for(int i=;i<=_n;i++)
- {
- if(dp[i][j-]!=-) dp[i][j] = dp[ dp[i][j-] ][j-];
- }
- }
- }
- void lca_init(int n)
- {
- _n = n;
- memset(dp,-,sizeof(dp));
- //_dfs(firstid,-1,0);
- _dfs(,-,);
- _init();
- }
- int lca_query(int a,int b)
- {
- if(deep[a]>deep[b]) swap(a,b);
- //调整b到a的同一高度
- for(int i=LN-;deep[b]>deep[a];i--)
- if(deep[b]-(<<i) >= deep[a]) b = dp[b][i];
- if(a == b) return a;
- for(int i=LN-;i>=;i--)
- {
- if(dp[a][i]!=dp[b][i]) a = dp[a][i],b = dp[b][i];
- }
- return dp[a][];
- }
- }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] = ;
- ccnt[s] = ;
- for(int i=;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]==)
- ccnt[s]++;
- }
- void dfs2(int s,int fa,long long num,long long tcnt)
- {
- sum[s] = down[s]+num+tcnt;
- for(int i=;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 = ;
- memset(pre,-,sizeof(pre));
- int n;
- scanf("%d",&n);
- for(int i=;i<=n;i++)
- {
- scanf("%d",g+i);
- }
- for(int i=;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=;
- for(int x=;x<=;x++)
- {
- if(saveall[x].size() == ) continue;
- //build virtual tree
- top = ;
- stk[top++] = saveall[x][];
- tree[ saveall[x][] ].clear();
- treew[ saveall[x][] ].clear();
- mark[saveall[x][]]=;
- for(int i=;i<saveall[x].size();i++)
- {
- int v = saveall[x][i];
- int plca = lca.lca_query(stk[top-], v);//最近公共祖先
- if(plca == stk[top-]) ;//不处理
- else
- {
- int pos=top-;
- while(pos>= && deep[ stk[pos] ]>deep[plca])
- pos--;
- pos++;
- for(int j=pos;j<top-;j++)
- {
- tree_add(stk[j],stk[j+],deep[stk[j+]]-deep[stk[j]]);
- }
- int prepos = stk[pos];
- if(pos == )
- {
- tree[plca].clear(),treew[plca].clear(),stk[]=plca,top=;
- mark[plca] = ;
- }
- else if(stk[pos-] != plca)
- {
- tree[plca].clear(),treew[plca].clear(),stk[pos]=plca,top=pos+;
- mark[plca] = ;
- }
- else top = pos;
- tree_add(prepos,plca,deep[prepos]-deep[plca]);
- }
- tree[v].clear();
- treew[v].clear();
- stk[top++] = v;
- mark[v] = ;
- }
- for(int i=;i<top-;i++)
- {
- tree_add(stk[i], stk[i+], deep[stk[i+]]-deep[stk[i]]);
- }
- //构建好了虚树,然后就是两次dfs
- nn = (int)saveall[x].size();
- dfs1(saveall[x][],-);
- dfs2(saveall[x][],-,,);
- long long tans=;
- for(int i=;i<saveall[x].size();i++)
- tans += sum[ saveall[x][i] ];
- tans /= ;
- ans += sign[x]*tans;
- }
- cout<<ans<<endl;//时间,内存。
- return ;
- }
青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)的更多相关文章
- floyd算法 青云的机房组网方案(简单)
青云的机房组网方案(简单) 青云现在要将 nn 个机房连成一个互相连通的网络.工程师小王设计出一个方案:通过在 nn 个机房之间铺设 n-1n−1 条双向的光纤,将所有的机房连接.可以假设数据在两个机 ...
- 2016 计蒜之道 初赛 第一场 D 青云的机房组网方案 (虚树)
大意: 给定树, 点$i$的点权为$a_i$, 求$\sum\limits_{a_i \perp a_j}dis(i,j)$ 中等难度可以枚举每条边的贡献, 维护子树内每个数出现次数$a$, 转化为求 ...
- PC-网络教程之宽带小型组网方案
由于某些家庭或小型局域网用户的各种需求和设备不同,所以继续写出几个组网方案让大家参考参考.如有错误之处,欢迎大家多多指点. 1,用网桥实现增加接入点(比如你有5台机子要上网,而你的小型路由只有4个接口 ...
- NB-IOT/LoRa/Zigbee无线组网方案对比
物联网设备节点组网存在2种组网方式, 无线组网和有线组网. 无线组网我们常见到的有Zigbee,LoRa, NB-IOT等,其中Lora/NB-IOT属于LPWAN技术,LPWAN技术有覆盖广.连接多 ...
- 5G组网方案:NSA和SA
目录 5G组网的8个选项 独立组网(SA) 选项1 选项2 选项5 选项6 总结 非独立组网(NSA) 选项3系列 选项3 选项3a 选项3x 选项7系列 选项4系列 选项8 演进路线 5G组网的8个 ...
- poj 2342 Anniversary party 简单树形dp
Anniversary party Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 3862 Accepted: 2171 ...
- HDU 1796How many integers can you find(简单容斥定理)
How many integers can you find Time Limit: 12000/5000 MS (Java/Others) Memory Limit: 65536/32768 ...
- hdu4705 Y 简单树形DP 2013多校训练第十场 J题
题意:求一棵树中不在一条链中的三个点的对数. 转化一下,用总对数减去在一条链上的三点对数即可. 考虑经过根节点,然后可能是不同的子树中各选一个:或者是子树中选一个,然后当前节点为根的子树以外的节点选一 ...
- 将简单的lambda表达式树转为对应的sqlwhere条件
1.Lambda的介绍 园中已经有很多关于lambda的介绍了.简单来讲就是vs编译器给我带来的语法糖,本质来讲还是匿名函数.在开发中,lambda给我们带来了很多的简便.关于lambda的演变过程可 ...
随机推荐
- 字符编码的过滤器Filter(即输入的汉字,能在页面上正常显示,不会出现乱码)
自定义抽象的 HttpFilter类, 实现自 Filter 接口 package com.lanqiao.javaweb; import java.io.IOException; import ja ...
- 如何利用java把文件中的Unicode字符转换为汉字
有些文件中存在Unicode字符和非Unicode字符,如何利用java快速的把文件中的Unicode字符转换为汉字而不影响文件中的其他字符呢, 我们知道虽然java 在控制台会把Unicode字符直 ...
- Cube Stacking
Cube Stacking Time Limit: 2000MS Memory Limit: 30000K Total Submissions: 21350 Accepted: 7470 Case T ...
- Poj(1466),最大独立集,匈牙利算法
题目链接:http://poj.org/problem?id=1466 Girls and Boys Time Limit: 5000MS Memory Limit: 10000K Total S ...
- C#中通过三边长判断三角形类型(三角形测试用例)
对于<编程之美>P292上关于三角形测试用例的问题,题目是这样的: 输入三角形的三条边长,判断是否能构成一个三角形(不考虑退化三角形,即面积为零的三角形),是什么样的三角形(直角.锐角.钝 ...
- 2016CCPC东北地区大学生程序设计竞赛 1008 HDU5929
链接http://acm.hdu.edu.cn/showproblem.php?pid=5929 题意:给你一种数据结构以及操作,和一种位运算,最后询问:从'栈'顶到低的运算顺序结果是多少 解法:根据 ...
- 织梦cms PHPcms 帝国cms比较
现在建网站不需要请程序员从基础的程序开发做起了,有专业的建站工具,CMS是使用最广泛的建站工具.CMS是Content Management System 现在建网站不需要请程序员从基础的程序开发做起 ...
- Redis的WEB界面管理工具phpRedisAdmin
下载地址:http://down.admin5.com/php/75024.html 官方网址:https://github.com/ErikDubbelboer/phpRedisAdmin
- linux ssh 使用深度解析(key登录详解)
SSH全称Secure SHell,顾名思义就是非常安全的shell的意思,SSH协议是IETF(Internet Engineering Task Force)的Network Working Gr ...
- linux下的./本质
不知道从什么时候对于./的感觉就是这是一条运行命令,因为你要运行某个文件的时候就用./ 但是这个显然是错误的./表述的是当前目录 .就是表示当前目录的.至于为什么运行当前目录下的 文件需要加上./原因 ...