仙人掌 && 圆方树 || 静态 + 动态 (差动态)

动态圆方树(LCC)已弃疗 四月也应该要退役了 是OI的谎言

大半天没有一个正经点的教程的 不过这也不是个正经东西 比较冷门

那啥 猫某的仙人掌的课件放这了 提取码: 8gtq 里面讲的很清楚了 这里还有一个

好了 相信大家都懂了

第一阶段(仙人掌图)

初识仙人掌 主要根据定义乱搞

例如说 这道 题目 (网址不同)

如果 dp 的话 首先考虑树上最长距离怎么求

方法一:树形 dp 使 f [p][0/1] 表示到 p 点往下能走的最长距离和第二长的距离 然后枚举

然而并没有什么用 如果最长的路和第二长的路 是经过了同一仙人掌的两条不同下去再合起来的

0ms 就 GG 了........

方法二:随便一点遍历到与它距离最远的点 再从该点遍历到离该点最远的点

但是图上行不通!例如这个图 (专门 YY 了个很漂漂的仙人掌出来)

花了我好久搞的图片

于是你会惊奇地发现 如果我们从 天蓝色 的点 开始搜

你会跑到 粉粉 的点 那里

然后再跑到三个绿色的点之一 得出直径为 9

然而事实上直径应该是 两个 浅绿色 的点 长度为 10

 

可以看出 仙人掌这种东西着实毒瘤

 

方法三:针对仙人掌的环对其进行轰杀

嘛,不就出现了环么 那我们处处针对它

考虑改进方法一 方法二的话就算了,难道要每个仙人掌枚举删边吗 =-= 极端情况全是三点仙人掌 复杂度 O(3^{\frac{n}{3}}\cdot n) 算了

(话说我分析的复杂度是对的吗)

于是就开始像 树形 dp 一样 然后环的话 我开始用的是 裁半 的方法更新下去 像这样 结果.......

inline void update1(int p)
{
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		if (!is[b]) f[p] = max(f[p],f[b] + 1);
}
inline void update2(int p)
{
	ans1 = ans2 = 0;
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		if (!is[b]) update(f[b]);
	ans = max(ans,ans1 + ans2 + 1);
}
void getf(int p)
{
	stack[++t] = p;
	o[p] = 1;
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (dfn[b] > dfn[p] && !o[b]) getf(b);
	if (dfn[p] == low[p])
	if (stack[t] == p)
	{
		--t;
		for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
			if (dfn[b] > dfn[p]) f[p] = max(f[p],f[b] + 1);
	} else {
		memset(is,0,sizeof(is));
		int len = 0;
		while (stack[t] != p)
		cir[++len] = stack[t--],is[cir[len]] = 1; --t,is[p] = 1;
		update1(cir[(len >> 1) + 1]);
		update1(cir[len >> 1]);
		for (int i = (len >> 1) + 2,j = cir[i] ; i <= len ; ++ i,j = cir[i])
		for (int a = first[j],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		{
			if (is[b] && dfn[b] < dfn[j]) continue;
			f[j] = max(f[j],f[b] + 1);
		}
		for (int i = ((len + 1) >> 1) - 1,j = cir[i] ; i > 0 ; -- i,j = cir[i])
		for (int a = first[j],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		{
			if (is[b] && dfn[b] < dfn[j]) continue;
			f[j] = max(f[j],f[b] + 1);
		}
		for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		{
			if (dfn[b] < dfn[p]) continue;
			f[p] = max(f[p],f[b] + 1);
		}
	}
}
void getdp(int p)
{
	stack[++t] = p;
	o[p] = 1;
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		if (dfn[b] > dfn[p] && !o[b]) getdp(b);
	if (dfn[p] == low[p])
	if (stack[t] == p)
	{
		--tot,ans1 = ans2 = 0;
		for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
			if (dfn[b] > dfn[p]) update(f[b]);
		ans = max(ans,ans1 + ans2 + 1);
	} else {
		memset(is,0,sizeof(is));
		int len = 0;
		while (stack[t] != p)
		cir[++len] = stack[t--],is[cir[len]] = 1; --t,is[p] = 1;
		update2(cir[(len >> 1) + 1]);
		update2(cir[len >> 1]);
		for (int i = (len >> 1) + 2,j = cir[i] ; i <= len ; ++ i,j = cir[i])
		{
			ans1 = ans2 = 0;
			for (int a = first[j],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
			{
				if (is[b] && dfn[b] < dfn[j]) continue;
				update(f[b]);
			}
			ans = max(ans,ans1 + ans2 + 1);
		}
		for (int i = ((len + 1) >> 1) - 1,j = cir[i] ; i > 0 ; -- i,j = cir[i])
		{
			ans1 = ans2 = 0;
			for (int a = first[j],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
			{
				if (is[b] && dfn[b] < dfn[j]) continue;
				update(f[b]);
			}
			ans = max(ans,ans1 + ans2 + 1);
		}
		ans1 = ans2 = 0;
		for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
			if (dfn[b] > dfn[p]) update(f[b]);
		ans = max(ans,ans1 + ans2 + 1);
	}
}

于是 因为一些毒瘤的路径重复原因 调了两个 20 节点的数据 过程都炸掉了

但居然还有 30 分 我的 4.4K Bytes 果然还是没有白打的

然后颓了好几个月没搞 再回来的时候已经崩溃了 (题目的原因加上生活中的原因)

当然我知道上面几行 和 我的 l j 代码 诸君是不想看的

转战 正经 dp 1.7K Bytes 诸君放心

 

 

首先我们深知这种图遍历时 不像普通图一样可以到处乱撞

如果它在环里 它不会跑到另一个环里遍历两个及以上的点 可以理解为不相关

如果不在环里 就像树一样 搜到头就回来了

因此我们使用无向图 Tarjan 即可以充分判断

话说无向图 Tarjan 只需要在有向图的程序里面加上 判断父亲 即可 如果有需要就用数组 没有就放子程序里

然后为了方便 我们顺便在遍历完图后 从最下面开始更新答案 像这样

void tarjan(int p) {
	dfn[p] = low[p] = ++tot;
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (b != fa[p]) {
		!dfn[b] ? fa[b] = p,dep[b] = dep[p] + 1,tarjan(b),low[p] = min(low[p],low[b])
				: low[p] = min(low[p],dfn[b]); //经典的Tarjan不解释了 dep是找环长用的
		if (low[b] > dfn[p]) ans = max(ans,dp[p] + dp[b] + 1),dp[p] = max(dp[p],dp[b] + 1);
	} b的连通分量的根大于p的搜索顺序 说明不在同一环中 照常更新
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		if (fa[b] != p && dfn[p] < dfn[b]) getcir(p,b); //这里搜到环了就直接搞环
} //从下往上更新答案 无后效性

处理环的话就是一次性把一整个环都搞出来 在这之前我们必然保证 dfn 比该环大的点 都已经更新完了 因为是 dfs

无后效性嘛 好了 那我们泰然自蒻地进入处理毒瘤环的环节

边长必定为 1 这个很好办了

为了不每个点绕一圈更新答案 我们考虑单调队列优化

因为最优的答案更新肯定小于半个环 我们不看环下面连的点 我们走环到环上点肯定是一半就能到两边了 所以把环断了再复制上一倍 每次更新半个环

子程序里面就只有复制环和单调队列了 队列里面也就是更新下答案 就不注释了

反正连我都懂的你们不可能看不懂是不是 所以下放这段的代码

void getcir(int x,int y) {
	int siz = dep[y] - dep[x] + 1,t = siz;
	for (int a = y ; a != x ; a = fa[a]) bot[t--] = dp[a]; bot[t] = dp[x];
	for (int a = 1 ; a <= siz ; ++ a) bot[a + siz] = bot[a];
	int l = 0,r = 0; que[0] = 1;
	for (int a = 2 ; a <= siz << 1 ; ++ a) {
		while (l <= r && a - que[l] > siz >> 1) ++ l;
		ans = max(ans,bot[a] + a + bot[que[l]] - que[l]);
		while (l <= r && bot[que[r]] - que[r] < bot[a] - a) -- r;
		que[++r] = a;
	}
	for (int a = 2 ; a <= siz ; ++ a) dp[x] = max(dp[x],bot[a] + min(a - 1,siz - a + 1));
}

好了 下面就放完整代码了 至于题目输入什么的 也不注释了 这个太无脑了 =-=

这道 题目 (网址不同) 的代码再放一下 =w=

#include <cstdio>
#define N 50010
inline int r() {
	char q = getchar(); int x = 0;
	while (q < '0' || q > '9') q = getchar();
	while ('0' <= q && q <= '9') x = x * 10 + q - 48,q = getchar();
	return x;
}
struct edge{int to,ne;}e[200010];
int first[N],dfn[N],low[N],dep[N],bot[N << 1],que[N],fa[N],dp[N],tot,ans;
inline int max(int x,int y) {return x > y ? x : y;}
inline int min(int x,int y) {return x < y ? x : y;}
void add(int x,int y) {
	e[++tot].ne = first[x],e[tot].to = y,first[x] = tot;
	e[++tot].ne = first[y],e[tot].to = x,first[y] = tot;
}
void getcir(int x,int y) {
	int siz = dep[y] - dep[x] + 1,t = siz;
	for (int a = y ; a != x ; a = fa[a]) bot[t--] = dp[a]; bot[t] = dp[x];
	for (int a = 1 ; a <= siz ; ++ a) bot[a + siz] = bot[a];
	int l = 0,r = 0; que[0] = 1; //话说这里r和我的快读重名了不过并没有什么事(好像慢了点)
	for (int a = 2 ; a <= siz << 1 ; ++ a) {
		while (l <= r && a - que[l] > siz >> 1) ++ l;
		ans = max(ans,bot[a] + a + bot[que[l]] - que[l]);
		while (l <= r && bot[que[r]] - que[r] < bot[a] - a) -- r;
		que[++r] = a;
	}
	for (int a = 2 ; a <= siz ; ++ a) dp[x] = max(dp[x],bot[a] + min(a - 1,siz - a + 1));
}
void tarjan(int p) {
	dfn[p] = low[p] = ++tot;
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (b != fa[p]) {
		!dfn[b] ? fa[b] = p,dep[b] = dep[p] + 1,tarjan(b),low[p] = min(low[p],low[b])
				: low[p] = min(low[p],dfn[b]);
		if (low[b] > dfn[p]) ans = max(ans,dp[p] + dp[b] + 1),dp[p] = max(dp[p],dp[b] + 1);
	}
	for (int a = first[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		if (fa[b] != p && dfn[p] < dfn[b]) getcir(p,b);
}
int main() {
	int n = r(),m = r();
	for (int b,x,k ; m > 0 ; -- m) {
		k = r(),x = r(); while (--k)
		b = r(),add(x,b),x = b;
	} tot = 0,tarjan(1),printf("%d\n",ans);
	return 0;
}

 

 

 

第二阶段(圆方树)

好了好了圆方树搞定了 打程序全程靠概念 yy 加上各种判断 感觉自己打的很毒瘤就是了 不过也就 4KBytes 不到

然后题目链接放这里了 又是板子题

感谢 @Harry_bh 提供的 hack 数据 让我受益匪浅emmm

话说打圆方树我用的是树剖嘛 像我这种会树剖不会倍增 会线段树不会树状数组的 实在是很少见了

而且我的代码太奇怪了感觉肯定很有问题 可能是乱讲一通

 

构造

搬一下 ImmortalCO 的课件里面的几句话

考虑为边设定边权,先随便取一个圆点当根,所有圆圆边的边权和 原图中一致

对于每一条圆方边: 如果它是方点的父边,则定义它的边权为 0,否则定义其边权为 「这个圆点到方点的父亲的最短路的长度」

现在,如果两点的 LCA 是圆点,则两点的最短路就是两点的圆方树上带权距离(所有环都在已经决定了走较短一侧)

否则,我们还需要考虑 LCA 这个环走哪一侧,用树链剖分或倍增 求出询问的两个点分别是在这个方点的哪两个子树中(即求出是环上的哪两个点),然后环上取较短的一侧

 

这里我参(zhao)照(ban)了之前那道 仙人掌图II 的遍历方法 然后在找到环的时候 将其 环权值 赋值到 即将连接的方点上

我们没必要每个点转一次环环 沿环遍历的同时 我们是从一边到达该点的 记录下来 循环完一圈后整个环的权值也记录下来了 然后再一个点一个点将 之前那段路的权值 和 环减去那段路的值 取 min 即可

下放一下这段代码

void getcir(int x,int y) {
	int len = tep[y] - tep[x] + 1,t = len; //len记录环上点数 t是指针
	ll lon = 0; //lon记录当前走的边的权值和
	for (int p = y ; p != x ; p = ta[p]) bot[t--] = p; bot[t] = x; //通过父亲数组取点
	for (int h = 1 ; h < len ; )
	for (int a = est[bot[h]],b = e[a].to ; ; a = e[a].ne,b = e[a].to)
		if (b == bot[h + 1]) {lon = lon + e[a].v,dis[++h] = lon; break;}
	for (int a = est[y],b = e[a].to ; ; a = e[a].ne,b = e[a].to)
		if (b == x) {lon = lon + e[a].v; break;} //搜环权值
	cir[++m] = lon; //记录环权值
	for (int a = 1 ; a <= len ; ++ a) addf(bot[a],m,min(lon - dis[a],dis[a]));
} //(上面)圆方加边
void tarjan(int p) { //用tarjan找环
	dfn[p] = low[p] = ++tnt;
	for (int a = est[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (b != ta[p]) {
		!dfn[b] ? tep[b] = tep[p] + 1,ta[b] = p,tarjan(b),low[p] = min(low[p],low[b])
				: low[p] = min(low[p],dfn[b]);
		if (low[b] > dfn[p]) addf(p,b,e[a].v);
	}
	for (int a = est[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		if (ta[b] != p && dfn[p] < dfn[b]) getcir(p,b);
}

那么构造说完了 我们来讲

 

建树

@142857cs 提供了树上前缀和的思路 感觉很好但我太顽固了实在是不想打 于是树剖加上线段树存权值

就是树剖的两个 dfs 啦 不过注意在 dfs1里要加上边权化点权 如果不会树剖边化点的可以去看看这个板子题

直接放代码了 这是两个深搜

void dfs1(int p) {
	dep[p] = dep[fa[p]] + 1,++siz[p];
	for (int a = fst[p],b = f[a].to ; a ; a = f[a].ne,b = f[a].to)
		if (b != fa[p]) {
		v[b] = f[a].v,fa[b] = p,dfs1(b),siz[p] += siz[b];
		if (siz[son[p]] < siz[b]) son[p] = b;
	}
}
void dfs2(int p,int an) { //an就是ancestor了,由于一些重名的原因..
	top[p] = an;
	id[p] = ++tot;
	oid[tot] = p;
	if (!son[p]) return;
	dfs2(son[p],an);
	for (int a = fst[p],b = f[a].to ; a ; a = f[a].ne,b = f[a].to)
		if (b != fa[p] && b != son[p]) dfs2(b,b);
}

这是一个建树

void build(int l,int r,int len) {
	if (l == r) {tr[len] = v[oid[l]]; return;}
	int mid = (l + r) >> 1;
	build(l,mid,len << 1);
	build(mid + 1,r,len << 1 | 1);
	tr[len] = tr[len << 1 | 1] + tr[len << 1];
}

 

查询

查询着实是毒瘤

考虑方点圆点?不止!

这里再次感谢 @Harry_bh 让误入歧途的我改过自新步入正轨

因为方点下面跳的两个点可能是两轻边呢=-= 那么下面来讲讲

lca 为圆点

直接搜索搜完了看看顶上那个点序号是不是大于 n 就好了

lca 为方点

首先我们要减去方点连的两圆点的权值

因为点权记录的是这个圆点到方点的父亲的最短路的长度 我们最后又不一定要跑过去

 

然后下面来说说边的问题

如果两个都是轻边 那么可以通过前驱来记录

如果其中一个是重边 那么我们就要把那个什么的点的前驱改成 lca 的 重儿子 不然都不知道掉到哪里去了

那放一下代码 首先是求 lca 的

ll out(int x,int y) {
	int fx = x,fy = y;//前驱 这个不赋值都可以
	ll ans = 0;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x,y),swap(fx,fy);
		ans += get(1,m,1,id[top[x]],id[x]);
		fx = top[x],x = fa[top[x]];
	} //跳树剖
	if (x != y) {
		if (dep[x] > dep[y]) swap(x,y),swap(fx,fy);
		ans += get(1,m,1,id[x] + 1,id[y]);
	} //记录终焉路径
	if (x <= n) return ans; //lca为圆点赶快退掉
	if (fy[fa] != fx[fa]) fy = son[x]; //把重链上的点提上来
	ans = ans - v[fx] - v[fy]; //减去多余路径
	if (tep[fx] > tep[fy]) swap(fx,fy); //这个因为父亲数组的原因要按dep排
	return ans + geft(fx,fy,cir[x]);
}

然后是 get 和 geft 这两个东西

get 就是线段树找连续一段的权值 这个树剖模板里有的 不多加阐述

geft 其实就是找两点的最短路径啦 某hkr 说这个也可以搞前缀记录 不过我太懒了 每个询问又跑了一遍环

所以应该是会被 hack 的 因为假如查询的环大 我这个要跑大半圈......

注释放代码里面吧

ll get(int l,int r,int len,int i,int j) {
	if (i <= l && r <= j) return tr[len];
	int mid = (l + r) >> 1; ll ans = 0;
	if (i <= mid) ans += get(l,mid,len << 1,i,j);
	if (mid < j) ans += get(mid + 1,r,len << 1 | 1,i,j);
	return ans;
}
ll geft(int x,int y,int cirdis) { //cirdis是环的总权值 之前tarjan的时候记录了的
	ll ans = 0;
	for (int p = y ; p != x ; p = ta[p]) //通过父亲找到两点距离
	for (int a = est[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (b == ta[p]) {ans = ans + e[a].v; break;}
	return min(cirdis - ans,ans); //取最小 因为两条路嘛
}

 

好了 接下来是总代码 随便加点注释吧

#include <algorithm>
#include <cstring>
#include <cstdio>
#define N 20010
#define M 100010
#define ll long long
#define swap std::swap
inline int re() {
	int x = 0; char w = getchar();
	while (w < '0' || w > '9') w = getchar();
	while ('0' <= w && w <= '9') x = x * 10 + w - 48,w = getchar();
	return x;
}
struct edge{int ne,to; ll v;}e[M],f[M];
int est[N],fst[N],dfn[N],low[N],tep[N],bot[N],ta[N],fr[N]; //这些是找环用的
int siz[N],son[N],dep[N],top[N],oid[N],fa[N],id[N]; //这些是树剖用的 名字比较像
int tr[N << 2],v[N];
int tot,tnt,n = re(),m = re(),q = re();
ll cir[N],dis[N];
template <typename T> T min(T x,T y) {return x < y ? x : y;}
void adde(int x,int y,int z) {
	e[++tot].ne = est[x],e[tot].to = y,e[tot].v = z,est[x] = tot;
	e[++tot].ne = est[y],e[tot].to = x,e[tot].v = z,est[y] = tot;
}
void addf(int x,int y,ll z) {
	f[++tot].ne = fst[x],f[tot].to = y,f[tot].v = z,fst[x] = tot;
	f[++tot].ne = fst[y],f[tot].to = x,f[tot].v = z,fst[y] = tot;
}
void getcir(int x,int y) {
	int len = tep[y] - tep[x] + 1,t = len;
	ll lon = 0;
	for (int p = y ; p != x ; p = ta[p]) bot[t--] = p; bot[t] = x;
	for (int h = 1 ; h < len ; )
	for (int a = est[bot[h]],b = e[a].to ; ; a = e[a].ne,b = e[a].to)
		if (b == bot[h + 1]) {lon = lon + e[a].v,dis[++h] = lon; break;}
	for (int a = est[y],b = e[a].to ; ; a = e[a].ne,b = e[a].to)
		if (b == x) {lon = lon + e[a].v; break;}
	cir[++m] = lon;
	for (int a = 1 ; a <= len ; ++ a) addf(bot[a],m,min(lon - dis[a],dis[a]));
} //找环加方点 顺便记录环长度
void tarjan(int p) {
	dfn[p] = low[p] = ++tnt;
	for (int a = est[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (b != ta[p]) {
		!dfn[b] ? tep[b] = tep[p] + 1,ta[b] = p,tarjan(b),low[p] = min(low[p],low[b])
				: low[p] = min(low[p],dfn[b]);
		if (low[b] > dfn[p]) addf(p,b,e[a].v);
	}
	for (int a = est[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
		if (ta[b] != p && dfn[p] < dfn[b]) getcir(p,b);
}
void dfs1(int p) {
	dep[p] = dep[fa[p]] + 1,++siz[p];
	for (int a = fst[p],b = f[a].to ; a ; a = f[a].ne,b = f[a].to)
		if (b != fa[p]) {
		v[b] = f[a].v,fa[b] = p,dfs1(b),siz[p] += siz[b];
		if (siz[son[p]] < siz[b]) son[p] = b;
	}
}
void dfs2(int p,int an) {
	top[p] = an;
	id[p] = ++tot;
	oid[tot] = p;
	if (!son[p]) return;
	dfs2(son[p],an);
	for (int a = fst[p],b = f[a].to ; a ; a = f[a].ne,b = f[a].to)
		if (b != fa[p] && b != son[p]) dfs2(b,b);
}
void build(int l,int r,int len) {
	if (l == r) {tr[len] = v[oid[l]]; return;}
	int mid = (l + r) >> 1;
	build(l,mid,len << 1);
	build(mid + 1,r,len << 1 | 1);
	tr[len] = tr[len << 1 | 1] + tr[len << 1];
}
ll get(int l,int r,int len,int i,int j) {
	if (i <= l && r <= j) return tr[len];
	int mid = (l + r) >> 1; ll ans = 0;
	if (i <= mid) ans += get(l,mid,len << 1,i,j);
	if (mid < j) ans += get(mid + 1,r,len << 1 | 1,i,j);
	return ans; //线段树查询链上权值和
}
ll geft(int x,int y,int cirdis) {
	ll ans = 0;
	for (int p = y ; p != x ; p = ta[p])
	for (int a = est[p],b = e[a].to ; a ; a = e[a].ne,b = e[a].to)
	if (b == ta[p]) {ans = ans + e[a].v; break;}
	return min(cirdis - ans,ans);
} //找环上距离通过之前的ta数组找较深点的父亲跳上去 然后取最小值
ll out(int x,int y) {
	int fx = x,fy = y; //记前驱
	ll ans = 0;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x,y),swap(fx,fy);
		ans += get(1,m,1,id[top[x]],id[x]);
		fx = top[x],x = fa[top[x]];
	} //跳树剖
	if (x != y) {
		if (dep[x] > dep[y]) swap(x,y),swap(fx,fy);
		ans += get(1,m,1,id[x] + 1,id[y]);
	}
	if (x <= n) return ans; //如果lca是圆点就跳出去
	if (fy[fa] != fx[fa]) fy = son[x];
	ans = ans - v[fx] - v[fy]; //如果是方点就去掉方点下面两点权值
	if (tep[fx] > tep[fy]) swap(fx,fy);
	return ans + geft(fx,fy,cir[x]); //跑环
}
int main() {
	for (int z,y,x ; m > 0 ; -- m) x = re(),y = re(),z = re(),adde(x,y,z);
	m = n,tot = 0,tarjan(1),dfs1(1),tot = 0,dfs2(1,1),build(1,m,1); //m到后来是存圆方树上点数的
	for (int y1,x1 ; q > 0 ; -- q) x1 = re(),y1 = re(),printf("%lld\n",out(x1,y1));
	return 0;
}

 

 

 

第三阶段(动态圆方树)

这个我不会,实在找不到正常点的标

VFleaKing的我看不懂啊 =-= 到时候问一下InFleaKing吧(汗

完结撒花

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值