还不会做! 树上的gcd 树分治 UOJ33
题目链接:http://uoj.ac/problem/33
题解链接:http://vfleaking.blog.uoj.ac/blog/38
现在感觉到了做OI的层层递进的思路的伟大之处,作为一个大学才开始接触C的人只能orz了
算法一:
傻逼暴力+lca,所以树O(n*n*logn)
所以10分
算法二:(orz我竟然看了半天)
对于随机生成的树,那么树的高度都是log层的,所以省略去算法一中的傻逼暴力。因为每一层的树高都是log,所以我们只需要暴力树根u,然后以u为根,dfs(u)的所有子节点,并且用cnt[h]记录高度为h的子树的个数即可。最后我们就是暴力h1和h2,然后用cnt[h1]*cnt[h2],然后加入到ans[gcd(h1, h2)]里面即可。因为h1和h2的高度是log,所以我们最终复杂度为O(N * logn * logn)。并且dfs也都是dfs子树,子树的平均分摊也肯定是logn次
所以30分
算法三:(早上看的时候完全没有反应过来,上完一下午的课回来再看发现,还是很难= =)
通过算法二的提示我们发现,我们只需要知道ans[gcd(h1, h2)]而已,所以,我们假定d=gcd(h1, h2),那么对于每一条链,设他的高度为hi,那么他为d倍数的个数为hi/d个,
策爷的代码,怪我水平不够,代码看不懂
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cstdio>
//#include<ctime>
#define MAX 200005
using namespace std;
int n;
struct edge{int v,next;}e[MAX];int etot=;int g[MAX];
void addedge(int u,int v){///建立有根树
e[etot].v=v;e[etot].next=g[u];g[u]=etot++;
}
typedef long long ll;
ll ans[MAX]={};///ans(i)表示两棵子树间深度为i的倍数的值的个数
ll ans2[MAX]={};
int h[MAX],pre[MAX];///h表示目前树的高度,pre(x)表示节点x的fa
int off[MAX]={};///off(i)表示目前是否对i节点打标记,就表示是否成为过重心 /**orz 这重心找的太强了,比dfs不知道快多少*/
int qu[MAX],sz[MAX],bobo[MAX];//这个bobo应该是bool的意思,表示是否能选择
int findc(int rt){
int p=,q=;
qu[q++]=rt;///就是queue
while(p!=q){///bfs对所有子节点进行初始化(当然,也可以在这里进行pre节点的修改)
int u=qu[p++];
bobo[u]=sz[u]=;
for (int i=g[u];~i;i=e[i].next)if(!off[e[i].v])///如果没有访问过,就刚入bfs的节点
qu[q++]=e[i].v;
}
for (int i=q-;i>=;i--){///迭代使用
if(bobo[qu[i]] && sz[qu[i]]*>=q)return qu[i];///重心肯定是树size的一半,所以sz肯定也是重心的一半左右
sz[pre[qu[i]]]+=sz[qu[i]];///对他的父亲节点的sz进行更新
if(sz[qu[i]]*>q)bobo[pre[qu[i]]]=;///如果目前节点的sz*2比q大了,那么父亲节点也肯定比他大了,所以对他的父亲节点都是false
}
} int nub[MAX];///统计这一棵子树,深度为h的有几个
/**统计深度为h的有几个,并且返回最大深度*/
int bfs(int rt,int *nub,int hh){///注意,这里的hh可能会发生改变的
int p=,q=;qu[q++]=rt;
while(p!=q){///bfs出所有的节点
int u=qu[p++];
for (int i=g[u];~i;i=e[i].next)if(!off[e[i].v])qu[q++]=e[i].v;
}
///h在main函数的输入中就已经得到了
int hmax=h[qu[q-]]-h[rt]+hh;///得到最大深度,因为bfs到的最大深度就是
for (int i=;i<=hmax;i++)nub[i]=;///对目前深度一下的进行初始化
for (int i=;i<q;i++){
nub[h[qu[i]]-h[rt]+hh]++;///统计目前深度的cnt的个数
}
/*if (hh == 0){
for (int i = 0; i < 10; i++){
printf("%d ", nub[i]);
}
cout << endl;
}
if (hh == 0) printf("hmax = %d\n", hmax);*/
return hmax;
} int tmp[MAX];///tmp[j]就是保存所有子树中被j整除的个数
ll tmpsq[MAX];///tmpsq[j]是保存了所有子树中自身两两搭配的个数
int nsu[MAX];///nsu表示所有子树的高度为j的数目
int tmpnu[MAX],*start[MAX],tmph[MAX];
int ord[MAX];
int cmp(int i,int j){return tmph[i]<tmph[j];}
int ind=;
int check[MAX]={},val[MAX]; void work(int rt){
int c=findc(rt);///找到重心
off[c]=;///对重心进行标记
/*u和v是在同一棵子树里面*/
int hmax=;
///暴力所有的重心的子节点
//printf("cetroid = %d\n", c);
//这里从来不计算高度=0的时候的东西
for (int i=g[c];~i;i=e[i].next)if(!off[e[i].v]){
int h=bfs(e[i].v,nub,);///这里之所以是1,因为他是重心的子节点
if(h>hmax){
///表示高度为j的是1,并且在这里初始化
for (int j=hmax+;j<=h;j++)
nsu[j]=,tmp[j]=tmpsq[j]=;
hmax=h;
}
for (int j=;j<=h;j++){
nsu[j]+=nub[j];///nsu表示所有子树的高度为j的数目
int sum=;
///k是j的倍数,sum保存能被j整除的数的个数
for (int k=j;k<=h;k+=j) sum+=nub[k];
///tmp[j]就是保存所有子树中被j整除的个数
///tmpsq[j]是保存了所有子树中自身两两搭配的个数
tmp[j]+=sum; tmpsq[j]+=1ll*sum*sum;
}
}
/*但是存在一个疑问点就是,如果两个都是2*d怎么解决呢?*/
///tmp[i]*tmp[i]表示所有子树的两两搭配,但是不包括自身的两两搭配
for (int i=;i<=hmax;i++)
ans[i]+=(1ll*tmp[i]*tmp[i]-tmpsq[i])>>; int u,num=;///num表示往上爬几次
int *st=tmpnu;///st为tmpnu数组
///往重心上面的结点爬 for (u=pre[c]; u && !off[u]; u=pre[u]){
off[u]=;///对父亲结点打标记
start[++num]=st;///让start[++num]等于st数组,并且复制给他
ord[num]=num;///记录编号
tmph[num]=bfs(u,st,);///这里之所以为0因为st是根
///因为u是之前的fa,所以这里要+1,表示的就是往右边移动一步 //表明st的移动并不对start存在影响
/*
printf("num = %d\n", num);
cout << "st1" << endl;
for (int j = 0; j < 10; j++){
printf("%d ", st[j]);
}
cout << endl; printf("start1\n");
for (int i = 0; i < 10; i++)
printf("%d ", start[num][i]);
cout << endl;
*/
st+=tmph[num]+;///然后让st指针移动maxn deep+1步,把所有的东西给清除掉?
//for (int j = 0; j <= tmph[num] + 1; j++) st[j] = 0;
/*
printf("tmph[%d] = %d\n", num, tmph[num]);
cout << "st2" << endl;
for (int j = 0; j < 10; j++){
printf("%d ", st[j]);
}
cout << endl; cout << "start2" << endl;
for (int i = 0; i < 10; i++)
printf("%d ", start[num][i]);
cout << endl;
*/
}
///对所有的东西标记去除,因为这里表示的就是不断地往上面走
for (int v=pre[c]; v!=u; v=pre[v]) off[v]=;
/*
后面这里完全看不懂啊TAT
*/
for (int i=;i<=hmax;i++){
ans2[i+]+=nsu[i];
ans2[i+num+]-=nsu[i];
}
///根据树的高度进行排序
sort(ord+,ord++num,cmp);
int tms=;
for (int h=,cur=;cur<=num;h++){
ind++;
///找到高度>=h的,因为ord是排序好了的
while(cur<=num && tmph[ord[cur]]<h)cur++;
for (int i=cur;i<=num;i++){
int s=,t;
///加上高度是j的倍数的东西
for (int j=h;j<=tmph[ord[i]];j+=h)s+=start[ord[i]][j];
if(check[ord[i]%h]==ind)t=val[ord[i]%h],tms++;
else{
int ss=h-(ord[i]-)%h-;t=;
for (int j=ss;j<=hmax;j+=h)t+=nsu[j],tms++;
check[ord[i]%h]=ind;
val[ord[i]%h]=t;
}
ans[h]+=1ll*s*t;
}
}
///也就是说,重心必须是目前的rt
if(c!=rt)work(rt);
///继续dfs
for (int i=g[c];~i;i=e[i].next)if(!off[e[i].v])work(e[i].v);
} int main(){
nsu[]=;///自己到自己的树高就是0
memset(g,-,sizeof(g));
scanf("%d",&n);
for (int i=;i<=n;i++){///输入
int x;scanf("%d",&x);
h[i]=h[x]+;pre[i]=x;///因为x是h[i]的fa,所以h[i]的深度比h[x]要大1.然后在此时记录i的父亲是x
addedge(x,i);///建立单向边,即有根树
if(x>=i)return ;///因为题目保证了i>x,所以这个for循环里面可以这么写
}
work();
for (int i=n-;i>=;i--){
for (int j=*i;j<=n-;j+=i) ans[i]-=ans[j];
}
for (int i=;i<=n-;i++) ans2[i]+=ans2[i-];
for (int i=;i<=n-;i++) printf("%lld\n",ans[i]+ans2[i]);
return ;
}
/*
7
1
2
3
3
1
2
*/
然后就是这位大牛的代码,终于看懂了,是我太菜了,晚上我再重敲一遍
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std; int read()
{
int x=,f=; char ch=getchar();
while (ch<'' || ch>'') {if (ch=='-') f=-; ch=getchar();}
while (ch>='' && ch<='') {x=x*+ch-''; ch=getchar();}
return x*f;
}
#define maxn 200010 int n,m,fa[maxn],root,block,siz;
struct EdgeNode{int next,to;}edge[maxn<<];
int head[maxn],cnt;
///插入双向边
void add(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;}
void insert(int u,int v) {add(u,v); add(v,u);}
/**/
int size[maxn],maxx[maxn]; bool visit[maxn]; ///得到重心,root就是重心
void Getroot(int now,int fa){
size[now]=; maxx[now]=;
for (int i=head[now]; i; i=edge[i].next)
if (edge[i].to!=fa && !visit[edge[i].to])
{
Getroot(edge[i].to,now);
size[now]+=size[edge[i].to];
maxx[now]=max(maxx[now],size[edge[i].to]);
}
maxx[now]=max(maxx[now],siz-size[now]);
if (maxx[now]<maxx[root]) root=now;
} int num[maxn],deep[maxn];///deep表示目前节点的深度,num[i]表示深度为i的节点有几个
int tmp;///表示最大深度
///得到最大深度
void DFSdeep(int now,int fa){
num[deep[now]]++;
for (int i=head[now]; i; i=edge[i].next)
if (edge[i].to!=fa && !visit[edge[i].to]){
deep[edge[i].to]=deep[now]+;
DFSdeep(edge[i].to,now);
}
if (deep[now]>tmp) tmp=deep[now];
} long long S[maxn],Ans[maxn],ans[maxn];
long long tim[maxn],Num[maxn],Tim[maxn],f[][];
/*
tim(i)表示当前子树里面的距离为i的倍数的数目
Tim(i)表示除了当前子树之外的其他子树的距离是i的倍数的数目
S(i)=tim[i]*Tim[i]表示两个子树之间相乘,就是ans(i)
num(i)就记录当前子树内距离根为i的有几个点
Num(i)就表示除了当前子树外,距离根为i的有几个点 f(i,j)表示目前深度为tmp,走j的倍数步的节点的个数是多少? 然后Ans[i]只记录(u, v)在同一条链上的,即LCA(u,v) = u 或者 LCA(u, v) = v
*/
void GetAns(int now)
{
visit[now]=;
int maxd=,Maxd=;///maxd表示当前子树的最大的deep,Maxd表示其他子树目前最大的Maxdeep
for (int i=head[now]; i; i=edge[i].next)///暴力子节点,且不暴力fa
if (!visit[edge[i].to] && edge[i].to!=fa[now])
{
deep[edge[i].to]=;///对刚开始的位置定义深度为1
tmp=;///tmp表示最深的深度
DFSdeep(edge[i].to,now);///寻找最深的深度
if (tmp>maxd) maxd=tmp;///修改最大深度
for (int j=; j<=tmp; j++)
for (int k=j; k<=tmp; k+=j)
tim[j]+=num[k];
for (int j=; j<=tmp; j++)///Ans[j]在这个时候加入,因为高度为j的一定是自己的gcd
Num[j]+=num[j],Ans[j]+=num[j],S[j]+=tim[j]*Tim[j],
Tim[j]+=tim[j],tim[j]=,num[j]=;///对tim和num初始化
}
Num[]=;///高度为0的在这个时候放入
int zz=,ss=now;
for (int i=fa[now]; !visit[i]&&i; ss=i,i=fa[i])
{
tmp=; zz++;///zz表示往上面走几步
for (int j=head[i]; j; j=edge[j].next)///得到目前的最大深度
if (edge[j].to!=fa[i] && !visit[edge[j].to] && edge[j].to!=ss)
deep[edge[j].to]=,DFSdeep(edge[j].to,i);
if (tmp>Maxd) Maxd=tmp;
for (int j=; j<=tmp; j++)///计算为k的倍数的东西
for (int k=j; k<=tmp; k+=j)
tim[j]+=num[k];
///tt就是小的那个,tmp表示最大深度,block表示块的大小
int tt = min(tmp, block);
for (int j=; j<=tt; j++)
{
if (f[j][zz%j]==-)///小于sqrt(n)的我们提前储存起来分治,这样就防止复杂度过高
{
f[j][zz%j]=;
for (int k=(j-zz%j)%j; k<=maxd; k+=j){///请注意(j-zz%j)和(j-zz%j)%j之间的区别
f[j][zz%j]+=Num[k];
}
}
///zz%j其实是一样的
S[j]+=f[j][zz%j]*tim[j];
}
for (int j=block+; j<=tmp; j++)///如果>j的话tim[j]
for (int k=(j-zz%j)%j; k<=maxd; k+=j)
S[j]+=Num[k]*tim[j];
///不放入Num和Tim之中
for (int j=; j<=tmp; j++) tim[j]=,num[j]=;
/*这个Ans是做啥的?*/
Ans[zz]++;///因为每次往上面走一步,所以要加上从重心开始到这个点的cnt
}
int l=,r=;///这个的作用是,莫队?,表示区间[i-zz, i-1]
/*这一步也看不懂*/
long long tmpans=;
/*
5
1
2
3
4
*/
/*
printf("cetroid = %d\n", now);
for (int i = 0; i <= maxd + zz; i++)
printf("%d ", Num[i]);
cout << endl; printf("zz = %d maxd = %d\n", zz, maxd);
*/
/**
终于弄明白了这里是什么意思了,Ans(i)的定义是不变的,所以i就表示距离是i的有几个,然后之前不是有一个向上
爬的过程嘛,另LCA为这个向上爬的每一个点,那么要满足在zz个之中深度在i的只有区间[i-zz,i-1],因为还有一个
节点并没有算上去!
*/
for (int i=; i<=zz+maxd; i++){///往上走了zz步,然后又+上原来的最大深度,所以i表示最大深度
tmpans += r+<i ? Num[++r]:;
tmpans -= l+zz<i ? Num[l++]:;
Ans[i]+=tmpans;
printf("i = %d l = %d r = %d Ans[%d] = %I64d tmpans = %I64d\n", i, l, r, i, Ans[i], tmpans);
}
int tt = min(Maxd, block);
///清空
for (int i=; i<=tt; i++)
for (int j=; j<=i-; j++)
f[i][j]=-;
for (int i=; i<=maxd; i++) Num[i]=,Tim[i]=;
///这里是重新开始分治
for (int i=head[now]; i; i=edge[i].next)
if (!visit[edge[i].to]){
root=;
siz=size[edge[i].to];
Getroot(edge[i].to,now);
GetAns(root);
}
} int main(){
n=read(); block=(int)sqrt(n);
for (int i=; i<=n; i++)
fa[i]=read(),insert(fa[i],i);
maxx[root]=0x7fffffff;
siz=n;
memset(f,-,sizeof(f));
Getroot(,);///得到重心
GetAns(root);///dfs从重心开始
for (int i=n-; i; i--){
ans[i]=S[i];
for (int j=i+i; j<=n-; j+=i)///为因为S(i)表示能被i整除的,但是可能存在两个都是大于i的东西的,所以我们这里要容斥一下
ans[i]-=ans[j];
}
for (int i=; i<=n-; i++) printf("%lld\n",ans[i]+Ans[i]);
return ;
}
还不会做! 树上的gcd 树分治 UOJ33的更多相关文章
- 树上的构造 树分治+树重心的性质 Codeforces Round #190 (Div. 2) E
http://codeforces.com/contest/322/problem/E E. Ciel the Commander time limit per test 1 second memor ...
- 【BZOJ-1468】Tree 树分治
1468: Tree Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 1025 Solved: 534[Submit][Status][Discuss] ...
- 【线段树分治 01背包】loj#6515. 「雅礼集训 2018 Day10」贪玩蓝月
考试时候怎么就是没想到线段树分治呢? 题目描述 <贪玩蓝月>是目前最火爆的网页游戏.在游戏中每个角色都有若干装备,每件装备有一个特征值 $w$ 和一个战斗力 $v$ .在每种特定的情况下, ...
- 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分
树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...
- 5.15 牛客挑战赛40 E 小V和gcd树 树链剖分 主席树 树状数组 根号分治
LINK:小V和gcd树 时限是8s 所以当时好多nq的暴力都能跑过. 考虑每次询问暴力 跳父亲 这样是nq的 4e8左右 随便过. 不过每次跳到某个点的时候需要得到边权 如果直接暴力gcd的话 nq ...
- 【BZOJ3460】Jc的宿舍(树上莫队+树状数组)
点此看题面 大致题意: 一棵树,每个节点有一个人,他打水需要\(T_i\)的时间,每次询问两点之间所有人去打水的最小等待时间. 伪·强制在线 这题看似强制在线,但实际上,\(pre\ mod\ 2\) ...
- BZOJ 2588: Spoj 10628. Count on a tree 树上跑主席树
2588: Spoj 10628. Count on a tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/J ...
- [POJ1741]树上的点对 树分治
Description 给一棵有n个节点的树,每条边都有一个长度(小于1001的正整数). 定义dist(u,v)=节点u到节点v的最短路距离. 给出一个整数k,我们称顶点对(u,v)是合法的当且仅当 ...
- BZOJ_4238_电压_树上差分+dfs树
BZOJ_4238_电压_树上差分+dfs树 Description 你知道Just Odd Inventions社吗?这个公司的业务是“只不过是奇妙的发明(Just Odd Inventions)” ...
随机推荐
- iOS开发 常见错误
一.NSAppTransportSecurity 错误提示:NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL ...
- HDU 5418 Victor and World 允许多次经过的TSP
题目链接: hdu: http://acm.hdu.edu.cn/showproblem.php?pid=5418 bestcoder(中文): http://bestcoder.hdu.edu.cn ...
- C++ Primer Plus学习:第九章
C++第九章:内存模型与名称空间 C++在内存中存储数据方面提供了多种选择.可直接选择保留在内存中的时间长度(存储持续性)以及程序哪一部分可以访问数据(作用域和链接)等. 单独编译 程序分为三个部分: ...
- 【leetcode】54.Spiral Matrix
Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...
- Geek荣耀大会总结
0.0 首先没有被抽中, 其次可乐真难喝,再次我没有去拍无人机合影,再再次还是很受打击的. 1.0 其实 对geek 和1024大会无感,主要原因 没有三倍加班费的节日在我眼里都不是节日. 上面只是简 ...
- 【Python】python操作mysql
pymysql模块对mysql进行 import pymysql # 创建连接 conn = pymysql.connect(host=, user='root', passwd='root', db ...
- 51nod-1222-最小公倍数计数
题意 给到 \(a,b\) ,求 \[ \sum _{i=a}^b\sum _x\sum _y[x\le y][\text{lcm}(x,y)=i] \] 即最小公倍数在 \([a,b]\) 中的有序 ...
- bzoj1318[spoj 744] Longest Permutation
题意 给出一个长度为n的,所有元素大小在[1,n]的整数数列,要求选出一个尽量长的区间使得区间内所有元素组成一个1到区间长度k的排列,输出k的最大值 n<=1e5 分析 不会做,好菜啊.jpg ...
- 51nod 1526 分配笔名(字典树+贪心)
题意: 班里有n个同学.老师为他们选了n个笔名.现在要把这些笔名分配给每一个同学,每一个同学分配到一个笔名,每一个笔名必须分配给某个同学.现在定义笔名和真名之间的相关度是他们之间的最长公共前缀.设笔名 ...
- NHibernate常见错误
Oracle 下必须用 Sequence [PrimaryKey(PrimaryKeyType.Sequence,"ID")] 1.提示 ORA-02289: 序列不存在 -- C ...