题目链接

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. 对于困难难度

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

思路概述:(抄了下)

  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[*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+容斥)的更多相关文章

  1. floyd算法 青云的机房组网方案(简单)

    青云的机房组网方案(简单) 青云现在要将 nn 个机房连成一个互相连通的网络.工程师小王设计出一个方案:通过在 nn 个机房之间铺设 n-1n−1 条双向的光纤,将所有的机房连接.可以假设数据在两个机 ...

  2. 2016 计蒜之道 初赛 第一场 D 青云的机房组网方案 (虚树)

    大意: 给定树, 点$i$的点权为$a_i$, 求$\sum\limits_{a_i \perp a_j}dis(i,j)$ 中等难度可以枚举每条边的贡献, 维护子树内每个数出现次数$a$, 转化为求 ...

  3. PC-网络教程之宽带小型组网方案

    由于某些家庭或小型局域网用户的各种需求和设备不同,所以继续写出几个组网方案让大家参考参考.如有错误之处,欢迎大家多多指点. 1,用网桥实现增加接入点(比如你有5台机子要上网,而你的小型路由只有4个接口 ...

  4. NB-IOT/LoRa/Zigbee无线组网方案对比

    物联网设备节点组网存在2种组网方式, 无线组网和有线组网. 无线组网我们常见到的有Zigbee,LoRa, NB-IOT等,其中Lora/NB-IOT属于LPWAN技术,LPWAN技术有覆盖广.连接多 ...

  5. 5G组网方案:NSA和SA

    目录 5G组网的8个选项 独立组网(SA) 选项1 选项2 选项5 选项6 总结 非独立组网(NSA) 选项3系列 选项3 选项3a 选项3x 选项7系列 选项4系列 选项8 演进路线 5G组网的8个 ...

  6. poj 2342 Anniversary party 简单树形dp

    Anniversary party Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 3862   Accepted: 2171 ...

  7. HDU 1796How many integers can you find(简单容斥定理)

    How many integers can you find Time Limit: 12000/5000 MS (Java/Others)    Memory Limit: 65536/32768 ...

  8. hdu4705 Y 简单树形DP 2013多校训练第十场 J题

    题意:求一棵树中不在一条链中的三个点的对数. 转化一下,用总对数减去在一条链上的三点对数即可. 考虑经过根节点,然后可能是不同的子树中各选一个:或者是子树中选一个,然后当前节点为根的子树以外的节点选一 ...

  9. 将简单的lambda表达式树转为对应的sqlwhere条件

    1.Lambda的介绍 园中已经有很多关于lambda的介绍了.简单来讲就是vs编译器给我带来的语法糖,本质来讲还是匿名函数.在开发中,lambda给我们带来了很多的简便.关于lambda的演变过程可 ...

随机推荐

  1. c# Beginlnvoke 委托

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  2. Spring框架bean的配置(2):SpEL:引用 Bean、属性和方法。。。

    将这些架包放入在工程目录下建立的lib文件夹里,并解压 commons-logging-1.1.1 spring-aop-4.0.0.RELEASE spring-beans-4.0.0.RELEAS ...

  3. Bean的生命周期

    Bean的生命周期 原文:http://997004049-qq-com.iteye.com/blog/1729793 任何一个事物都有自己的生命周期,生命的开始.生命中.生命结束.大家最熟悉的应该是 ...

  4. ubuntu下查看cpu信息

    查看CPU信息cat /proc/cpuinfo 几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核( ...

  5. Apache common pool2 对象池

    对象池的容器:包含一个指定数量的对象.从池中取出一个对象时,它就不存在池中,直到它被放回.在池中的对象有生命周期:创建,验证,销毁,对象池有助于更好地管理可用资源,防止JVM内部大量临时小对象,频繁触 ...

  6. IDEA打war包

    一 第一步创建一个web application:expolded 选择当前项目 二 新建一个web application: Archive 选择刚刚新建的Expoded  “For ‘...... ...

  7. web错误

    “/Web”应用程序中的服务器错误. 服务器 'LD-PC' 上的 MSDTC 不可用. 说明: 执行当前 Web 请求期间,出现未经处理的异常.请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误 ...

  8. Spring 框架 详解 (一)

    Spring是分层的JavaSE/EE full-stack(一站式) 轻量级开源框架 * 分层: * SUN提供的EE的三层结构:web层.业务层.数据访问层(持久层,集成层) * Struts2是 ...

  9. iOS深入学习(UITableView系列4:使用xib自定义cell)

    可以通过继承UITableViewCell重新自定义cell,可以像下面一样通过代码来自定义cell,但是手写代码总是很浪费时间, ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  10. Cheatsheet: 2015 01.01~ 01.31

    JAVA JVM Architecture Improving Lock Performance in Java 10 Best Java Tools That Every Java Programm ...