【UR #2】树上GCD

这道题是有根树点分治+烧脑的容斥+神奇的分块

因为是规定1为根,还要求LCA,所以我们不能像在无根树上那样随便浪了,必须规定父亲,并作特殊讨论

因为gcd并不好求,所以我们用容斥转化一下,求x为gcd的因数的个数,这样就可以随便统计了,个人觉得代码比题解要好懂。

又因为统计完重心的所有子树,还有重心的父亲,所以在这个分支块内沿着重心的父亲一路向上爬,这时候重心的子树到重心的父亲的距离是变的,所以我们用神奇的分块大法,分类讨论,$<=\sqrt{n}$使用数组记录答案,方便以后再用到的时候统计,$>\sqrt{n}$时直接暴力统计,因为此时统计的复杂度并不高。这样使时间复杂度空降时间复杂度为$O(n\sqrt{n})$。个人感觉题解说得太含糊了,一切都不如直接看代码明晰,或者说题解是帮助看懂代码的233

这道题细节太多了,跪了1天半终于AC了蛤蛤蛤蛤蛤蛤蛤

这道题我先膜了鏼爷的代码,因为各种指针看不懂啊,但学习了非递归求重心的新姿势,无限仰膜Orz!!!SDOI就是要练就各种非递归能力

然后我又膜了ShallWe的题解,学会了容斥统计的方法,无限仰膜啊!!!巧妙的特判和两个指针的移动使得容斥统计不重不漏,真是太神奇了!!!

最后用了charge教我的压常大法,把各种循环里面用来判断的“min”,“+”提前算出来,常数小到你根本无法想象!!!

最后我的代码在UOJ的时间效率上排到了rank1

持续沾沾自喜中。。。。。。。。。。。。。。。。。。。。

给出我的代码吧:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 200003;
void read(int &k) {
	k = 0; int fh = 1; char c = getchar();
	for(; c < ‘0‘ || c > ‘9‘; c = getchar())
		if (c == ‘-‘) fh = -1;
	for(; c >= ‘0‘ && c <= ‘9‘; c = getchar())
		k = (k << 1) + (k << 3) + c - ‘0‘;
	k = k * fh;
}

bool vis[N];
struct node {
	int nxt, to;
} E[N];
int qu[N], ct[N], n, m, fa[N], de, deep[N], cnt = 0, point[N], root, sz[N], boo[N];
ll t[503][503], cn[N], sct[N], scn[N], ans2[N], S[N];

void ins(int x, int y) {E[++cnt].nxt = point[x]; E[cnt].to =  y; point[x] = cnt;}
void findrt(int x) {
	int p = 0, q = 0; qu[++q] = x;
	while (p != q) {
		int u = qu[++p];
		boo[u] = sz[u] = 1;
		for(int tmp = point[u]; tmp; tmp = E[tmp].nxt)
			if (!vis[E[tmp].to])
				qu[++q] = E[tmp].to;
	}
	for(int i = q; i; --i) {
		if (boo[qu[i]] && sz[qu[i]] * 2 >= q) {root = qu[i]; return;}
		sz[fa[qu[i]]] += sz[qu[i]];
		if (sz[qu[i]] * 2 >= q)
			boo[fa[qu[i]]] = 0;
	}
}//非递归找重心!!!国家队rank3的鏼爷Orz给SDOIers带来福利

void BFS(int x) {
	deep[x] = 1;
	int p = 0, q = 0; qu[++q] = x;
	while (p != q) {
		int u = qu[++p]; ++ct[deep[u]];
		for(int tmp = point[u]; tmp; tmp = E[tmp].nxt)
			if (!vis[E[tmp].to])
				deep[E[tmp].to] = deep[u] + 1, qu[++q] = E[tmp].to;
	}
	de = deep[qu[q]];
}

void Q(int x) {
	vis[x] = 1;
	int up = 0, upp = 0;
	for(int tmp = point[x]; tmp; tmp = E[tmp].nxt)
		if (!vis[E[tmp].to]) {
			BFS(E[tmp].to);
			up = max(up, de);
			for(int i = 1; i <= de; ++i)
				for(int j = i; j <= de; j += i)
					cn[i] += ct[j];
			for(int i = 1; i <= de; ++i) {
				ans2[i] += ct[i];
				sct[i] += ct[i];
				S[i] += scn[i] * cn[i];
				scn[i] += cn[i];
				ct[i] = cn[i] = 0;
			}
		}

	sct[0] = 1;
	int step = 0, son = x, line, to;
	for(int i = fa[x]; !vis[i] && i; son = i, i = fa[i]) {
		++step; to = 0;
		for(int tmp = point[i]; tmp; tmp = E[tmp].nxt)
			if (!vis[E[tmp].to] && E[tmp].to != son)
				BFS(E[tmp].to), to = max(to, de);
		de = to;
		upp = max(upp, de);
		for(int j = 1; j <= de; ++j)
			for(int k = j; k <= de; k += j)
				cn[j] += ct[k];
		line = min(de, m);
		for(int j = 1; j <= line; ++j) {
			to = step % j;
			if (t[j][to] == -1) {
				t[j][to] = 0;
				for(int k = (j - to) % j; k <= up; k += j)
					t[j][to] += sct[k];
			}
			S[j] += t[j][to] * cn[j];
		}
		for(int j = m + 1; j <= de; ++j)
			for(int k = (j - step % j) % j; k <= up; k += j)
				S[j] += sct[k] * cn[j];
		for(int j = 1; j <= de; ++j)
			ct[j] = cn[j] = 0;
		++ans2[step];
	}

	//下面是向ShallWe学的
	int L = 1, R = 0, tot = step + up;
	ll now = 0;
	for(int i = 2; i <= tot; ++i) {
		if (R + 1 < i) now += sct[++R];
		if (L + step < i) now -= sct[L++];
		ans2[i] += now;
	}
	//仰膜上方ShallWe的代码

	line = min(m, upp);
	for(int i = 1; i <= line; ++i)
		for(int j = 0; j < i; ++j)
			t[i][j] = -1;
	for(int i = 0; i <= up; ++i)
		sct[i] = scn[i] = 0;

	if (son != x) {
		findrt(son);
		Q(root);
	}
	for(int tmp = point[x]; tmp; tmp = E[tmp].nxt)
		if (!vis[E[tmp].to]) {
			findrt(E[tmp].to);
			Q(root);
		}
}

ll ans[N];
int main() {
	read(n); m = (sqrt(n));
	for(int i = 2; i <= n; ++i) {
		read(fa[i]);
		ins(fa[i], i);
	}

	memset(t, -1, sizeof(t));
	findrt(1);
	Q(root);
	for(int i = n - 1; i; --i)
		for(int j = i + i; j < n; j += i)
			S[i] -= S[j];
	for(int i = 1; i < n; ++i)
		printf("%lld\n", S[i] + ans2[i]);	

	return 0;
}

已经没有什么好害怕的了,我想那还真是令人高兴啊

时间: 2024-08-28 05:30:10

【UR #2】树上GCD的相关文章

UOJ UR#2树上GCD(缓存)

#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} whi

[UOJ]#33. 【UR #2】树上GCD

题目大意:给定一棵有根树,边长均为1,对于每一个i,求树上有多少个点对,他们到lca距离的gcd是i.(n<=200,000) 做法:先容斥,求出gcd是i的倍数的点对,考虑长链剖分后从小到大合并计算答案,小的部分先把每个深度的数量变为每个深度的倍数的数量,然后若深度>k,直接到大的里面暴力,若深度<=k,我们在大的里面维护f[i][j]表示深度mod i(1<=i<=k)为j的点数,理论上k取n^0.5时达到最小复杂度O(n^1.5),实际上k比较小的时候常数较小.另外递归

uoj33 【UR #2】树上GCD

题目 大致是长剖+\(\rm dsu\ on\ tree\)的思想 先做一个转化,改为对于\(i\in[1,n-1]\)求出有多少个\(f(u,v)\)满足\(i|f(u,v)\),这样我们最后再做一个反演就好了 既然我们要求有多少对\(f(u,v)\)是\(i\)或\(i\)的倍数,我们需要在长剖的时候快速合并两边的信息,这个信息长得非常别致,形如到当前节点距离为\(i\)或\(i\)的倍数的节点个数 轻儿子这边还好说,我们直接暴力调和级数处理一下即可,但是这样的信息从中儿子哪里却非常不好继承

【UOJ】树上gcd

点分治 这道题还有很多种其它写法,什么长链剖分啦,启发式合并啦等等. 首先,我们可以把点对\((u,v)\)分成两类: 1.u到v的路径是一条链 2.u到v的路径不是一条链(废话) 对于第一类,显然\(f(u,v)\)就是链的长度,可以单独统计 对于第二类,就要在点分治上搞了 我们可以先计算出为d的倍数的点对数,最后容斥一下即可 在点分治中,我们取出当前子树的重心root,统计路径经过root的点对,那么又可以分成两类: A.u和v都在root的子树内 B.u和v一个在root的子树内,另一个不

开坑UR

会做几道算几道吧…… 开个坑比较能激励自己 强迫症buff+拖延症buff rating神马的?不要在意嘛 没写的都是坑 ~~~~~萌萌哒分割线~~~~~ UR#1 1 缩进优化 2 外星人 3 跳蚤国王下江南 UR#2 1 猪猪侠再战括号序列 2 跳蚤公路 3 树上GCD UR#3 1 核聚变反应强度 2 铀仓库 3 链式反应 UR#4 1 元旦三侠的游戏 只需目测就可以发现,我们可以table[b][a] 然后除了b=1的很长之外,其它都是sqrt(n)以下的 所以可以记忆化,然后b=1时把

uoj#370【UR #17】滑稽树上滑稽果

题目 低智选手果然刷不动uoj 首先考虑一下构造一棵树显然是骗你玩的,按位与这个东西越做越小,挂到链的最下面显然不会劣于挂到之前的某一个点下面,所以我们只需要求一个排列使得答案最小就好了 设\(A=\max(a_i)\),发现最优答案不可能要劣于反复对一个数取\(\rm and\)的答案,我们就有了一个\(O(nA)\)的暴力,设\(dp_i\)表示当前的\(\rm and\)和为\(i\),把这个\(i\)变成\(0\)的最小代价 但是有可能最后的\(\rm and\)和也不是\(0\),于是

(树上莫队)HDU - 5799 This world need more Zhu

题意: 两种询问: 1.询问以u为根的子树中出现的a次的数的和与出现b次的数的和的gcd. 2.询问u到v的树链中出现的a次的数的和与出现b次的数的和的gcd. 有点绕.. 分析: 因为自己不会树上莫队,所以学习了一波. 但是对于子树我还是有所经验,可以转成dfs序来做,之前有做过类似的题,比如这题. 然而对于树链有点懵逼,虽然我觉得也能用dfs序做,不过看大佬们的dfs序做的树链查询,也有点懵,感觉写起来很麻烦. 貌似是修改了dfs序,回溯的时候不再只是和进入时相同的序,而是独立的序. 还是感

[csu/coj 1079]树上路径查询 LCA

题意:询问树上从u到v的路径是否经过k 思路:把树dfs转化为有根树后,对于u,v的路径而言,设p为u,v的最近公共祖先,u到v的路径必定是可以看成两条路径的组合,u->p,v->p,这样一来便可以将判断条件转化为(LCA(u,k)=k  || LCA(v,k)=k) && LCA(k,p)=p.由于这个LCA询问里面需要用到中间结果p,所以这种方法用tarjan离线不行,只能用dfs+RMQ. 1 #pragma comment(linker, "/STACK:10

BC#40D GCD值统计

这题有点恶心,好多东西堆一起.刚开始看出来实质就是SG,于是很开心的敲了,然后发现统计GCD很烦,推了半天以为推出来了,写完后到最后一步,只想到暴力遍历.提交后TLE,倒回来想树上任意路径权值和的问题,这不是裸的树分治么,以前一眼能看出来的.只是最后再加个树分治略恶心.第二天早上突然发觉,统计GCD值的地方,实际上是n^2.5的,脑残了,复杂度都算不准了.问题实质就是: ∑(n,i=1)∑(m,j=1)gcd(i,j)=∑(n,i=1)∑(m,j=1)∑(d|i&d|j)?(d)=∑(min(n