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

动态圆方树(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吧(汗

完结撒花

### 方拆单模型中CAD块参照动态更新功能的实现方法 #### 1. 动态更新功能的核心需求 在方拆单模型中,CAD块参照的动态更新功能旨在实时同步块定义与块引用之间的属性和几何对象。具体需求包括: - **属性同步**:当块定义中的属性(如板件名称、编号、切割尺寸等)发生变化时,所有引用该块的实例自动更新[^2]。 - **几何对象同步**:修改块定义中的几何对象后,所有引用该块的实例需同步应用这些更改。 #### 2. 实现动态更新的技术方案 ##### (1)块定义与块引用的双向映射 为了实现动态更新,需建立块定义与块引用之间的双向映射关系。通过以下步骤实现: - **唯一标识符**:为每个块定义分配一个全局唯一的标识符(如UUID),并在所有引用该块的实例中记录此标识符。 - **索引表**:创建一个索引表,存储块定义与引用实例的映射关系。 以下是Python代码示例,展示如何构建块定义与引用实例的映射关系: ```python class BlockDefinition: def __init__(self, block_id, attributes, geometry): self.block_id = block_id self.attributes = attributes self.geometry = geometry self.references = [] # 引用实例列表 class BlockReference: def __init__(self, block_id, insert_point, scale_factors, rotation_angle): self.block_id = block_id self.insert_point = insert_point self.scale_factors = scale_factors self.rotation_angle = rotation_angle # 示例:构建映射关系 block_definitions = {} block_references = [] def add_block_definition(block_id, attributes, geometry): block_definitions[block_id] = BlockDefinition(block_id, attributes, geometry) def add_block_reference(block_id, insert_point, scale_factors, rotation_angle): reference = BlockReference(block_id, insert_point, scale_factors, rotation_angle) block_references.append(reference) if block_id in block_definitions: block_definitions[block_id].references.append(reference) ``` ##### (2)属性动态更新机制 当块定义的属性发生变化时,需遍历所有引用该块的实例,并同步更新其属性。以下是具体实现: - **属性传播**:通过索引表查找所有引用该块的实例,并将更新后的属性值赋给这些实例。 - **冲突处理**:如果某个引用实例的属性已被手动修改,则需提供冲突解决策略(如覆盖或保留手动修改)。 以下是属性动态更新的Python代码示例: ```python def update_block_attributes(block_id, new_attributes): if block_id in block_definitions: block_definition = block_definitions[block_id] block_definition.attributes.update(new_attributes) for reference in block_definition.references: reference.attributes = block_definition.attributes.copy() ``` ##### (3)几何对象动态更新机制 几何对象的动态更新涉及更复杂的变换操作,需考虑插入点、比例因子和旋转角度的影响。以下是实现步骤: - **几何对象提取**:从块定义中提取所有几何对象。 - **变换矩阵计算**:根据引用实例的插入点、比例因子和旋转角度,计算变换矩阵。 - **几何对象应用**:将变换矩阵应用于几何对象,并更新引用实例的显示内容。 以下是几何对象动态更新的Python代码示例: ```python import math def apply_transformation(geometry_objects, insert_point, scale_factors, rotation_angle): transformed_objects = [] # 计算变换矩阵 cos_theta = math.cos(math.radians(rotation_angle)) sin_theta = math.sin(math.radians(rotation_angle)) scale_x, scale_y, scale_z = scale_factors dx, dy, dz = insert_point for obj in geometry_objects: # 假设几何对象为点 (x, y, z) x, y, z = obj # 应用旋转 x_rotated = x * cos_theta - y * sin_theta y_rotated = x * sin_theta + y * cos_theta z_rotated = z # 应用缩放 x_scaled = x_rotated * scale_x y_scaled = y_rotated * scale_y z_scaled = z_rotated * scale_z # 应用平移 x_transformed = x_scaled + dx y_transformed = y_scaled + dy z_transformed = z_scaled + dz transformed_objects.append((x_transformed, y_transformed, z_transformed)) return transformed_objects def update_block_geometry(block_id): if block_id in block_definitions: block_definition = block_definitions[block_id] for reference in block_definition.references: reference.geometry = apply_transformation( block_definition.geometry, reference.insert_point, reference.scale_factors, reference.rotation_angle ) ``` #### 3. 方拆单模型中的特殊需求 ##### (1)孔位信息的动态更新 在方拆单模型中,孔位信息(如侧孔、正面孔、反面孔、通孔等)是关键属性之一。动态更新孔位信息时,需遵循以下规则: - **正面孔识别**:孔径表示为`A.B 1CD`,其中`A.B`为实际孔径,`CD`为深度。 - **反面孔识别**:孔径表示为`A.B 2CD`,其中`A.B`为实际孔径,`CD`为深度。 - **通孔识别**:直接创建通孔标注即可识别。 以下是孔位信息动态更新的Python代码示例: ```python def update_hole_information(block_id, holes): if block_id in block_definitions: block_definition = block_definitions[block_id] block_definition.holes = holes for reference in block_definition.references: reference.holes = holes.copy() ``` ##### (2)槽位信息的动态更新 槽位信息的动态更新需结合几何对象的变换矩阵进行调整。具体实现可参考几何对象动态更新机制。 #### 4. 性能优化建议 - **增量更新**:仅更新发生变动的部分,而非重新加载整个块定义。 - **缓存机制**:对频繁访问的块定义和引用实例使用缓存,减少重复计算。 - **多线程处理**:对于大规模CAD模型,可采用多线程技术加速动态更新过程。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值