记录
杂题
agc001f Wide Swap
去年联赛前写过一次,但是没什么印象,重新做了下。
原排列中交换的条件不好,将排列建出逆排列p,即 p[a[i]] = i,也就是a[i]的位置为i
现在 \(p_i\) 与 \(p_j\) 交换的条件为|i-j| = 1, \(|p_i - p_j| \geq k\) ,
于是发现如果任意的一对i,j, \(|p_i - p_j| < k\) ,那么 \(p_i\) 和 \(p_j\) 的相对位置一定不变
因为如果变化的话,肯定有一次交换是 \(p_i\) 和 \(p_j\) 交换
所以一些数之间的关系已经确定,可以建个DAG跑拓扑,至于边每个i分别找到后面第一个 \(p_j\) 大于 \(p_i\) 的和 \(p_j\) 小于 \(p_i\) 的j建就好了,边数 \(O(n)\) 的
现在我们要满足a的字典序最小,即让p中1出现地尽量靠前,满足此前提下2出现尽量靠前,依次类推...
注意这并不是让p字典序最小(字典序最小是前面的尽量小,这是小的尽量靠前)
此类问题有个经典的贪心做法,就是让其反序列的字典序最大,然后再reverse。
或者说是从后往前填数,对于一个位置,尽量填大的。
(正确性自己口胡了一下,在某一个位置决策的时候,填小点的数一定不如填大的更优,
因为那个小点的数可以填在前面使答案更优)
CF793F Julia the snail
离线,从左往右枚举右端点,更新左边的点作为左端点的答案,对于右端点r,有个绳子右端点为r,左端点为l,什么样的左端点可以被这根绳子更新,显然是左端点在l及之前的,能到达的最右边在l及之后的,所以我们只需要将[1,l]中权值大于等于l的更新为r即可,这就是个 Segment Tree Beats。
CF720D Slalom
dp
,扫描线,我们在每个矩形右下角右边一个方格处统计答案,对于处理到当前行的矩形
- 从下面绕的,由左面右侧边最近的还未结束的矩形右下角右边的那里转移过来,转移的值为两点横坐标之间的
dp
值的和, - 从上面绕的,还是保存在原位置,不作转移(最后通过区间求和统计答案)
然后处理当前行结束的矩形,将左右端点之间的dp
值清空就好了。
最后统计答案时,如果有未结束的矩形,p
就在最右边矩形右1
个的位置,否则p=1
,对[p,n]
的dp
值求个和就好了
代码
#include <set>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define L (rt * 2)
#define R (rt * 2 + 1)
#define fi first
#define se second
#define ll long long
#define rint register int
#define pb(x) push_back(x)
#define mp(x, y) make_pair(x, y)
#define pii pair < int , int >
using namespace std;
const int N = 1e6 + 10, M = 1e9 + 7;
int n, m, K;
int t[N * 4];
bool lz[N * 4];
vector < pii > vec, v1[N], v2[N];
multiset < pii > s;
int read(rint x = 0, bool f = 0, char ch = getchar()) {
for(; ch < '0' || ch > '9'; ch = getchar()) f = ch == '-';
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + (ch & 15);
return f ? -x : x;
}
void Up(rint rt) {
t[rt] = (t[L] + t[R]) % M;
}
void Down(rint rt) {
lz[rt] = 0;
t[L] = t[R] = 0;
lz[L] = lz[R] = 1;
}
void Modify(rint rt, rint l, rint r, rint x, rint v) {
if(l == r) return t[rt] = v, void();
rint mid = (l + r) / 2;
if(lz[rt]) Down(rt);
x <= mid ? Modify(L, l, mid, x, v) : Modify(R, mid + 1, r, x, v);
Up(rt);
}
void Clear(rint rt,rint l, rint r, rint x, rint y) {
if(x > y || !t[rt]) return;
if(x <= l && r <= y) return t[rt] = 0, lz[rt] = 1, void();
rint mid = (l + r) / 2;
if(lz[rt]) Down(rt);
if(x <= mid) Clear(L, l, mid, x, y);
if(y > mid) Clear(R, mid + 1, r, x, y);
Up(rt);
}
int Ask(rint rt, rint l, rint r, rint x, rint y) {
if(x > y) return 0;
if(x <= l && r <= y) return t[rt];
rint mid = (l + r) / 2, ret = 0;
if(lz[rt]) Down(rt);
if(x <= mid) (ret += Ask(L, l, mid, x, y)) %= M;
if(y > mid) (ret += Ask(R, mid + 1, r, x, y)) %= M;
return ret;
}
int main() {
n = read(), m = read(), K = read();
for(rint i = 1; i <= K; ++i) {
rint x = read(), y = read(), xx = read(), yy = read();
pii pa = mp(x, xx);
v1[y].pb(pa);
if(yy < m) v2[yy + 1].pb(pa);
}
Modify(1, 1, n, 1, 1);
for(rint i = 1; i <= m; ++i) {
vec.clear();
for(pii pa : v1[i]) {
rint x = pa.se + 1, xx = 1;
if(i > 1 && x <= n) {
auto it = s.lower_bound(mp(x + 1, 0));
if(it != s.begin()) xx = (*--it).se + 1;
if(xx < x) vec.pb(mp(x, Ask(1, 1, n, xx, x)));
}
}
for(pii pa : vec) Modify(1, 1, n, pa.fi, pa.se);
for(pii pa : v1[i]) {
s.insert(pa);
Clear(1, 1, n, pa.fi, pa.se);
}
for(pii pa : v2[i]) s.erase(s.find(pa));
}
if(s.empty()) printf("%d\n", t[1]);
else printf("%d\n", Ask(1, 1, n, (*s.rbegin()).se + 1, n));
return 0;
}
CF490F Treeland Tour
权值线段树合并,线段树上维护2
个值,线段树下标为i
的保存以权值为i
的数开头,伸向子树中的最长上升/下降链长度。
Dfs
到x
为根的子树,2
种贡献
- 经过
x
的路径,直接在每个儿子的线段树中查就好 - 不经过
x
的路径,在线段树合并的时候更新,因为是合并x
儿子的线段树,所以没有x
的信息,我们只需要在线段树上维护区间max
就可以在合并的时候更新答案了
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
int n, cnt, Ans, num, nc, root[N], head[N], a[N], b[N];
struct Edge {
int to, nxt;
} e[N * 2];
struct Node {
int ls, rs, v[2];
} t[N * 20];
int read(int x = 0, bool f = 0, char ch = getchar()) {
for(; ch < '0' || ch > '9'; ch = getchar()) f = ch == '-';
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + (ch & 15);
return f ? -x : x;
}
void Adde(int x, int y) {
e[++cnt] = (Edge){y, head[x]};
head[x] = cnt;
}
#define L(x) (t[x].ls)
#define R(x) (t[x].rs)
int Ask(int rt, int l, int r, int x, int y, int opt) {
if(x > y || !rt) return 0;
if(x <= l && r <= y) return t[rt].v[opt];
int mid = (l + r) / 2, ret = 0;
if(x <= mid) ret = max(ret, Ask(L(rt), l, mid, x, y, opt));
if(y > mid) ret = max(ret, Ask(R(rt), mid + 1, r, x, y, opt));
return ret;
}
void Add(int &rt, int l, int r, int p, int v, int opt) {
if(!rt) rt = ++nc;
t[rt].v[opt] = max(t[rt].v[opt], v);
if(l == r) return;
int mid = (l + r) / 2;
if(p <= mid) Add(L(rt), l, mid, p, v, opt);
else Add(R(rt), mid + 1, r, p, v, opt);
}
#define Cmx(a, b) (a = max(a, b))
int Merge(int rt1, int rt2) {
if(!rt1 || !rt2) return rt1 | rt2;
Cmx(t[rt1].v[0], t[rt2].v[0]);
Cmx(t[rt1].v[1], t[rt2].v[1]);
Cmx(Ans, t[L(rt1)].v[0] + t[R(rt2)].v[1]);
Cmx(Ans, t[L(rt2)].v[0] + t[R(rt1)].v[1]);
L(rt1) = Merge(L(rt1), L(rt2));
R(rt1) = Merge(R(rt1), R(rt2));
return rt1;
}
void Dfs(int x, int prt) {
int s0 = 0, s1 = 0;
for(int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(y == prt) continue;
Dfs(y, x);
int v0 = Ask(root[y], 1, num, 1, a[x] - 1, 0);
int v1 = Ask(root[y], 1, num, a[x] + 1, num, 1);
Cmx(Ans, max(s0 + v1, s1 + v0) + 1);
Cmx(s0, v0), Cmx(s1, v1);
root[x] = Merge(root[x], root[y]);
}
Add(root[x], 1, num, a[x], s0 + 1, 0);
Add(root[x], 1, num, a[x], s1 + 1, 1);
}
int main() {
n = read();
for(int i = 1; i <= n; ++i) a[i] = b[i] = read();
for(int i = 1; i < n; ++i) {
int x = read(), y = read();
Adde(x, y), Adde(y, x);
}
sort(b + 1, b + 1 + n);
num = unique(b + 1, b + 1 + n) - (b + 1);
for(int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + 1 + num, a[i]) - b;
Dfs(1, 0);
printf("%d\n", Ans);
return 0;
}
Alice 和 Bob 又在玩游戏
博弈论,检验当前各个树联通块的SG值异或和是否为0即可。
如何求一个树的SG
函数,假设x
在以i
为根的子树中,定义sg[i]
表示以i
为根的树的sg
值,t[x]
表示删去x
到i
路径上所有点得到的局面的的sg
值(也就是子树i
分成的各个联通块的sg
值异或和)
容易发现,sg[i]
为子树中所有点x
的t[x]
的mex
, \(t[i] = {xor}_{j \in son(i)}sg[j]\),子树中其他点的t
多的部分都是多异或了t[i]^sg[j]
(j
是那个点的一个祖先),所以写个01-trie
即可,支持插入,合并,整体异或,求mex
。
P4000 斐波那契数列
题意:
求斐波那契数列第 \(n\) 项模 \(p\) 的结果。\((n \leq 10^{3e7},p \leq 2^{31})\)
题解:
只需要对 \(n\) 模循环节或者其倍数即可
可以证明斐波那契数列在模 \(p\) 意义下循环节 \(\pi(p) \leq 6p\),因此介绍一种随机化方法求循环节倍数的方法
我们如果可以找到一组不相等的 \(i,j\) 满足 \(f(i)=f(j)\) 且 \(f(i-1)=f(j-1)\),那么 \(abs(i-j)\) 即为循环节倍数。
如果直接随机 \(i,j\),那么期望命中概率是 \(\mathcal{O}(p)\) 的。
利用生日悖论,我们可以每次随机一个数 \(i\),并记录 \(f(i)\) 和 \(f(i-1)\),如果对于当前的 \(i\) ,可以找到之前记录到的数与其匹配即可。期望命中概率为 \(\mathcal{O}(\sqrt p)\)
时间复杂度 \(\mathcal{O}(\sqrt{\pi(p)}log\sqrt{\pi(p)}+2^3log\pi(p))\)
CF633H Fibonacci-ish II
题意:
给定长度为 \(n\) 的序列 \(a_1,a_2,...,a_n\),\(q\) 次询问,每次询问一个区间 \(\left[l,r\right]\),输出将 \(\left[l,r\right]\) 中的数去重再排序后依次乘上斐波那契数列的和。\((1 \leq n \leq 30000,1 \leq q \leq 30000)\)
题解:
斐波那契数列结论:
\(F_{n+m}=F_{n-1}*F_{m}+F_{n}*F_{m+1}\)
证明:
由
\(\begin{pmatrix}F_{n+1}&F_{n}\\F_{n}&F_{n-1}\end{pmatrix}=\begin{pmatrix}1&1\\1&0\end{pmatrix}^{n}\)\((n \geq 1)\)
得
\(\begin{pmatrix}F_{n+m}&F_{n+m-1}\\F_{n+m-1}&F_{n+m-2}\end{pmatrix}\)
\(=\begin{pmatrix}1&1\\1&0\end{pmatrix}^{n+m-1}\)
\(=\begin{pmatrix}1&1\\1&0\end{pmatrix}^{n}\begin{pmatrix}1&1\\1&0\end{pmatrix}^{m-1}\)
\(=\begin{pmatrix}F_{n+1}&F_{n}\\F_{n}&F_{n-1}\end{pmatrix}\begin{pmatrix}F_{m}&F_{m-1}\\F_{m-1}&F_{m-2}\end{pmatrix}\)
根据矩阵乘法定义 \(F_{n+m}=F_{n-1}*F_{m}+F_{n}*F_{m+1}\)
证毕
考虑莫队,如何维护新加一个数或删除一个数答案变化量。
假设当前区间内去重排序后的数组为 \(a_1,a_2,...,a_{cnt}\),答案为 \(a_1F_1+a_2F_2+...+a_{cnt}F_{cnt}\)
加入一个点后,答案增加了 \(a_kF_k\),且后面的所有 \(a_iF_i\) 变为 \(a_iF_{i+1}\)
我们可以用权值线段树维护,并维护个标记 \(x\),表示区间内所有 \(a\) 需要乘的 \(F\) 下标的变化量。
原来权值为 \(\sum\limits_{i=l}^{r}a_iF_i\),现在变为 \(\sum\limits_{i=l}^{r}a_iF_{i+x}=F_x\sum\limits_{i=l}^{r}a_iF_{i-1}+F_{x+1}\sum\limits_{i=l}^{r}a_iF_i\)
因此,权值线段树上维护 \(\sum\limits_{i=l}^{r}a_iF_i\) 以及 \(\sum\limits_{i=l}^{r}a_iF_{i-1}\) 即可。
时间复杂度 \(\mathcal{O}(n\sqrt{n}logn)\)
另外注意本题会用到负数下标的斐波那契数,直接倒着推就可以。
Restore Atlantis
本题有 \(2\) 个关键点
- 询问是排除编号在一段区间矩形后的剩余矩形面积并
- 矩形值域很小
因为值域较小,可以考虑每个 \(1 \times 1\) 方格的贡献
又因为询问是排除一段区间,那么只要知道覆盖该 \(1 \times 1\) 方格的大矩形的最小编号与最大编号即可。
具体来说就是从左向右移动一条与 \(y\) 轴平行的直线,维护覆盖这条直线的矩形编号,并对于这条直线上的点,更新覆盖其的最小最大矩形编号,即扫描线+线段树维护优先队列。
维护出覆盖每个点的矩形的最小最大编号后,便可以将询问离线,按右端点排序,不难用树状数组维护答案。
时间复杂度为 \(\mathcal{O}(nlog^2n+4*10^6*logn)\)。
UTR #1
vfk的数据
将数字提取出来高精比较即可
pyx的难题
直接二分优先级排名,用堆模拟 \(check\) ,复杂度 \(nlog^2\),有 \(90\) 分
其实可以优化掉二分,首先将 \(x\) 的优先级定为 \(-1\) ,模拟一轮
考虑 \([t_x + 1,T]\) 这个区间, \(x\) 一定不可能在 \(T\) 之前完成,因为他优先级最低。
所以我们把在这个区间做过的题拿出来,并算出每道题花的时间,按优先级排序,
然后给优先级做个前缀和,因为保证有解,当 \(sum\) 等于 \(s_x\) 时,答案可以为大于当前优先级的最小优先级
这样就相当于 \(x\) 把这个区间最后面 \(s_x\) 段占领了,满足题意,复杂度 \(nlog\)
ydc的大树
结论:
- 一棵树的中心(直径中点)唯一
- 一棵树任意一个点到达最远点必经过中心
我们先将黑点形成的树的中心求出来,将中心提为根,首先算根的贡献(如果根是白色)
现在枚举不是根的白点 \(b\) ,考虑它是否截断了黑点 \(a\) 的路径
- \(b\) 是 \(a\) 的祖先, \(a\) 要经过根,所以必经过 \(b\) , \(b\) 能截断
- \(b\) 不是 \(a\) 的祖先,需要看一下除了 \(a\) 所在的根的那个子树,距离根最远点是否都在 \(b\) 子树内
第一种情况的黑点数量就是 \(b\) 子树中的黑点数量
第二种情况需要记录根的不同儿子中前三长的链分别是哪三个儿子(当然是黑点形成的链),以及每个点子树中黑点的最大深度及这种点的个数
最大值有 \(3\) 个及以上的儿子那么 \(b\) 就不会截断根其他儿子中的黑点,其他的也好想,讨论他们三个的黑点最大深度的大小关系即可算出当前枚举的白点的答案
UER #1
猜数
注意 n
是个平方数就行了
跳蚤OS
文件夹类的题一知不知道怎么才能更方便地去维护
有个方法就是我们在结尾添一个 /
,维护每个形如 xxx/
的单位就好了,根目录的 /
可以看成前面一个空串
这道题用trie
树,每当访问到 /
就去尝试跳快捷链接,最后输出时候直接跳父亲到根节点,倒序输出路径即可
DZY Loves Graph
注意审题!他只会撤回 上一次 操作
因为给你的边权是递增的,所以可以直接加进图中更新,每次删除就是删除最后的k
条边。
加1
条边或者删1
条边可以直接按秩合并并查集+栈(支持撤回),注意这个栈中元素个数就是存在的边的个数,维护修改的信息,全图是否联通,此刻树边权值和即可。
复杂度卡在不断地删k
条边然后撤回操作,解决方法是,如果当前删k
条边,我先输出栈中的少k
条边时候的答案,如果下次不是return
我再回退并查集的信息。
ps:一直30
分原因是栈中加入元素时要记得先初始化为0
,因为栈在不断push
和pop
,会有残留的之前的信息
UR #1
缩进优化
题目让求一个x,使得 \(\sum\limits_{i=1}^{n}a_i-(x-1)\sum\limits_{i=1}^{n}\lfloor\frac{a_i}{x}\rfloor\) 最小
因为 \(a_i\) 值域1e6
,所以存桶里,做个前缀和,对于每个x
,就可以 \(\frac{1e6}{x}\) 的复杂度求解了,总复杂度 \(\mathcal{O}(nlnn)\)
外星人
如果先模x
,那么所有大于等于x
的数都没有用了,我们将所有 \(a_i\) 从大到小排序后,就可以dp
了
f[i][j]
表示对于前i
个数模完后为j
的方案数,对于第 \(a_i\) ,处理完i-1
个的值为j
- 让 \(a_i\) 发挥作用,即
f[i][j % a_i] += f[i-1][j]
- 不让 \(a_i\) 发挥作用,后面有
n-i
个比 \(a_i\) 小的,所以 \(a_i\) 的位置有n-i
种方案(即跳过几个比他小的放 \(a_i\) ),即f[i][j] += f[i-1][j] * (n-i)
跳蚤国王下江南
这题不写了
UR #2
猪猪侠再战括号序列
一种构造方式是让前n个是(
,后n个是)
,每扫到一个)
就将其与后面第一个(
之间形成的区间翻转,因为这个区间只有最后一个是(
,所以不用 \(\mathcal{O}(n)\) 翻转,直接交换开头和结尾即可,可以单调指针找后面第一个(
,并且总次数 \(\leq n\)
跳蚤公路
树上GCD
我们可以求gcd
是x
倍数的路径条数,也就是由两条都是x
倍数的路径拼起来的方案数,以及单独考虑的向子树中伸出的一条链
单独伸入子树的好算,对于深度的桶a2
作个后缀和,即可求出有祖先关系的长度为i
的链有a2[i]
条
拼起来的,可以点分治,统计跨过当前重心的路径,然而对于这种路径在原树上又分为2
类
- 当前重心恰好是原树上这条路经两端点的
lca
- 当前重心在原树上这条路径两端点
lca
一侧的子树内
对于第一类,我们直接在点分治的时候组合就好,累加答案(注意是x
倍数的路径条数,对桶需要个nln
的处理)
第二类我们去跳父亲,然后在每个父亲处,遍历其他子树(当前遍历的所有点都是这个重心所分出的联通块内的)。得到的「父亲的链」与「重心到当前父亲+第一类的链」组合起来可以求出第二类的路径的贡献
但是注意这部分的组合不好写,我们可以类似的用个nln
的处理,得到父亲伸向其他儿子的链中,长度为x
倍数的有几条。设重心到当前父亲距离为d
,如果考虑对ans[i]
作贡献(i
的倍数的路径条数),我们会发现要求第一类的链的长度应满足len = (-d) mod i
,即满足一种模i
余数为j
的形式,设第一类的链中最大深度为d1
,这个暴力求一次是 \(\mathcal{O}(d1)\) 的,对于每个i
都求的话,复杂度显然太高。根号分治,我们用sm[i][j]
表示第一类的链中,长度模i
为j
的个数,对于 \(i \leq \sqrt{d1}\) 的处理出来,当查询模i
为j
的条数的时候
- \(i \leq \sqrt{d1}\) ,直接 \(\mathcal{O}(1)\) 在
sm
中查询 - \(i > \sqrt{d1}\) , \(\mathcal{O}(\sqrt{d1})\) 暴力求
所以对于每个i
,算出第二类的路径对ans[i]
的贡献,总复杂度 \(\mathcal{O}(n\sqrt{n})\)
还有一点, \(f_n = \sum\limits_{n|d}g_d\),已知f
求g
,可以直接倒着,容斥求,复杂度是 \(\mathcal{O}(nln)\) 的
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 200000 + 10, inf = 1e9;
int n, ec, rt, ts, mx;
int h[N], d[N], f[N], sz[N];
ll c[N], s[N], sm[510][510], a1[N], a2[N];
bool v[N];
struct Edge {
int t, n;
} e[N * 2];
void Add(int x, int y) {
e[++ec] = (Edge) {y, h[x]};
h[x] = ec;
}
void Getrt(int x, int prt) {
int nmx = 0; sz[x] = 1;
for(int i = h[x]; i; i = e[i].n) {
int y = e[i].t;
if(y == prt || v[y]) continue;
Getrt(y, x);
nmx = max(nmx, sz[y]);
sz[x] += sz[y];
}
nmx = max(nmx, ts - sz[x]);
if(mx > nmx) mx = nmx, rt = x;
}
void Getsz(int x, int prt) {
sz[x] = 1;
for(int i = h[x]; i; i = e[i].n) {
int y = e[i].t;
if(y == prt || v[y]) continue;
Getsz(y, x);
sz[x] += sz[y];
}
}
void Getd(int x, int prt, int dep, int &mxd) {
++c[dep], mxd = max(mxd, dep);
for(int i = h[x]; i; i = e[i].n) {
int y = e[i].t;
if(y == prt || v[y]) continue;
Getd(y, x, dep + 1, mxd);
}
}
void Solve(int x) {
v[x] = 1, Getsz(x, 0);
int d1 = 0;
memset(s, 0, 8 * (sz[x] + 1));
for(int i = h[x]; i; i = e[i].n) {
int y = e[i].t, d2 = 0;
if(y == f[x] || v[y]) continue;
Getd(y, x, 1, d2);
for(int j = 1; j <= d2; ++j) {
for(int k = j * 2; k <= d2; k += j) {
c[j] += c[k];
}
a1[j] += s[j] * c[j];
s[j] += c[j];
}
memset(c, 0, 8 * (d2 + 1));
d1 = max(d1, d2);
}
++s[0];
for(int i = d1; i >= 1; --i) {
for(int j = i * 2; j <= d1; j += i) {
s[i] -= s[j];
}
}
int kc = sqrt(d1);
for(int i = 1; i <= kc; ++i) {
memset(sm[i], 0, 8 * (i + 1));
for(int j = 0; j <= d1; ++j) {
sm[i][j % i] += s[j];
}
}
for(int p = f[x], la = x; p && !v[p]; la = p, p = f[p]) {
for(int i = h[p]; i; i = e[i].n) {
int y = e[i].t, d2 = 0;
if(v[y] || y == f[p] || y == la) continue;
Getd(y, p, 1, d2);
for(int j = 1; j <= d2; ++j) {
for(int k = j * 2; k <= d2; k += j) {
c[j] += c[k];
}
int res = ((d[p] - d[x]) % j + j) % j;
if(j <= kc) a1[j] += c[j] * sm[j][res];
else {
for(int k = res; k <= d1; k += j) {
a1[j] += c[j] * s[k];
}
}
}
memset(c, 0, 8 * (d2 + 1));
}
}
for(int i = h[x]; i; i = e[i].n) {
int y = e[i].t;
if(v[y]) continue;
ts = sz[y], mx = inf;
Getrt(y, x), Solve(rt);
}
}
int main() {
scanf("%d", &n);
for(int i = 2; i <= n; ++i) {
scanf("%d", &f[i]);
Add(i, f[i]);
Add(f[i], i);
d[i] = d[f[i]] + 1;
}
for(int i = 1; i <= n; ++i) ++a2[d[i]];
for(int i = n; i >= 1; --i) a2[i] += a2[i + 1];
mx = inf, ts = n;
Getrt(1, 0), Solve(rt);
for(int i = n; i >= 1; --i) {
for(int j = i * 2; j <= n; j += i) {
a1[i] -= a1[j];
}
}
for(int i = 1; i < n; ++i) printf("%lld\n", a1[i] + a2[i]);
return 0;
}