HDU 4812 D Tree

本文介绍了一道编程题的解决方法,利用树的分治思想结合哈希表来寻找满足条件的路径。通过对树进行递归划分找到重心,并以此为中心将问题分解,使用哈希表加速查找过程。

题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=4812

题意:给出一棵树,让你寻找一条路径,使得路径上的点相乘mod10^6+3等于k,输出路径的两个端点,按照字典序最小输出。

解法:这类问题很容易想到树的分治,每次找出树的重心,以重心为根,将树分成若干棵子树,然后对于每棵子树再一样的操作,现在就需要求一重心为根,寻找路径,依次遍历每一个子树,然后记录子树中点到根的权值的乘积X,然后通过在哈希表中寻找K×逆元(x),看是否存在,存在则更新答案,我这里用map来维护。


#include <bits/stdc++.h>
using namespace std;
typedef  long long LL;
const int maxn = 1e5+3;
const int mod = 1e6+3;
LL inv[mod];
struct edge{
	int to,next;
	edge(){}
	edge(int to,int next):to(to),next(next){}
}E[maxn*2];
int head[maxn],edgecnt,mi;
void add(int u, int v){
	E[edgecnt].to=v,E[edgecnt].next=head[u],head[u]=edgecnt++;
}
pair <int,int> ans;
int n,siz[maxn],mx[maxn],root,val[maxn];
LL K;
bool vis[maxn];
void init(){
	memset(head,-1, sizeof(head));
	edgecnt=0;
	memset(vis,0,sizeof(vis));
}
void dfssize(int u, int fa){//处理子树的大小
	siz[u]=1;
	mx[u]=0;
	for(int i=head[u];~i;i=E[i].next){
		int v=E[i].to;
		if(v!=fa&&!vis[v]){
			dfssize(v, u);
			siz[u]+=siz[v];
			if(siz[v]>mx[u]) mx[u]=siz[v];
		}
	}
}
void dfsroot(int r, int u, int fa){//求重心
	if(siz[r]-siz[u]>mx[u]) mx[u]=siz[r]-siz[u];
	if(mx[u]<mi) mi=mx[u],root=u;
	for(int i=head[u]; ~i; i=E[i].next){
		int v=E[i].to;
		if(v!=fa&&!vis[v]) dfsroot(r, v, u);
	}
}
void cal(int u, int fa, int length, unordered_map<int, int>&status){
	length = (LL)length*val[u]%mod;
	int &s=status[length];
	if(!s) s=u;
	else s=min(s,u);
	for(int i=head[u];~i;i=E[i].next){
		int v = E[i].to;
		if(v==fa || vis[v]) continue;
		cal(v, u, length, status);
	}
}
void solve(int u){
	mi=n;
	dfssize(u,0);
	dfsroot(u,u,0);
	u=root;
	vis[u]=1;
	unordered_map<int,int>preStatus;
	preStatus[val[u]]=u;
	for(int i=head[u];~i;i=E[i].next){
		int v=E[i].to;
		if(vis[v]) continue;
		unordered_map<int, int>nowStatus;
		cal(v,u,1,nowStatus);
		for(auto &p:nowStatus){
			int need = K*inv[p.first]%mod;
			auto it = preStatus.find(need);
			if(it==preStatus.end()) continue;
			int x=p.second;
			int y=it->second;
			if(x>y) swap(x,y);
			ans = min(ans, make_pair(x,y));
		}
		for(auto &p:nowStatus){
			int length = ((LL)p.first*val[u])%mod;
			int point = p.second;
			auto it = preStatus.find(length);
			if(it == preStatus.end())
				preStatus[length] = point;
			else
				it->second = min(point,it->second);
		}
	}
	for(int i=head[u]; ~i; i=E[i].next){
		int v = E[i].to;
		if(vis[v]) continue;
		solve(v);
	}
}
int main(){
	inv[0]=inv[1]=1;
	for(int i=2; i<mod; i++) inv[i] = (mod-mod/i)*inv[mod%i]%mod;
	while(~scanf("%d%lld",&n,&K)){
		init();
		for(int i=1; i<=n; i++) scanf("%d", &val[i]);
		for(int i=1; i<n; i++){
			int u,v;
			scanf("%d%d", &u,&v);
			add(u,v);
			add(v,u);
		}
		ans = make_pair(INT_MAX,INT_MAX);
		solve(1);
		if(ans.first==INT_MAX){
			puts("No solution");
		}else{
			printf("%d %d\n", ans.first,ans.second);
		}
	}
	return 0;
}


【激光质量检测】利用丝杆与步进电机的组合装置带动光源的移动,完成对光源使用切片法测量其光束质量的目的研究(Matlab代码实现)内容概要:本文研究了利用丝杆与步进电机的组合装置带动光源移动,结合切片法实现对激光光源光束质量的精确测量方法,并提供了基于Matlab的代码实现方案。该系统通过机械装置精确控制光源位置,采集不同截面的光强分布数据,进而分析光束的聚焦特性、发散角、光斑尺寸等关键质量参数,适用于高精度光学检测场景。研究重点在于硬件控制与图像处理算法的协同设计,实现了自动化、高重复性的光束质量评估流程。; 适合人群:具备一定光学基础知识和Matlab编程能力的科研人员或工程技术人员,尤其适合从事激光应用、光电检测、精密仪器开发等相关领域的研究生及研发工程师。; 使用场景及目标:①实现对连续或脉冲激光器输出光束的质量评估;②为激光加工、医疗激光、通信激光等应用场景提供可靠的光束分析手段;③通过Matlab仿真与实际控制对接,验证切片法测量方案的有效性与精度。; 阅读建议:建议读者结合机械控制原理与光学测量理论同步理解文档内容,重点关注步进电机控制逻辑与切片数据处理算法的衔接部分,实际应用时需校准装置并优化采样间距以提高测量精度。
11-06
<think>我们正在查找杭电OJ题目HDU1698的相关信息。根据引用内容,我们并没有直接看到HDU1698的题目内容,但我们可以根据题目编号1698推测这是一道关于线段树的题目(题目名为“Just a Hook”),因为它在杭电OJ中是一个经典的线段树应用问题。 题目大意: 通常,HDU1698的题目描述是:有一个长度为n的钩子,初始每个部分都由铜制成(价值为1)。然后有若干操作,每个操作将区间[a, b]的钩子部分替换成另一种材料(银价值为2,金价值为3)。最后要求整个钩子的总价值。 解题思路: 使用线段树进行区间更新(懒惰标记)。线段树的每个节点维护该区间内的材料类型(或者总价值,由于材料一致,所以也可以维护材料类型和区间长度来计算价值)。更新操作需要懒惰标记来优化,避免每次都更新到叶子节点。 代码结构: 1. 线段树节点:通常包括该区间内的总价值,以及懒惰标记(表示该区间被统一设置成的材料类型)。 2. 建树:初始化,整个区间都是铜,即价值为1。 3. 更新:将区间[a, b]设置成材料c(即价值c),更新线段树,并使用懒惰标记。 4. 查询:通常最后查询整个区间的总价值(根节点的值)。 参考引用[2]和[5]中提供了C++代码的示例,我们可以用类似的风格编写。 下面是我编写的HDU1698题目的C++代码(带注释): ```cpp #include <iostream> #include <cstdio> #define lson l, mid, rt<<1 #define rson mid+1, r, rt<<1|1 using namespace std; const int MAXN = 100000; int tree[MAXN<<2]; // 存储线段树,每个节点表示该区间的总价值 int lazy[MAXN<<2]; // 懒惰标记,记录该区间被设置成什么材料 void pushUp(int rt) { // 向上更新:节点的值等于左右儿子节点值的和 tree[rt] = tree[rt<<1] + tree[rt<<1|1]; } void pushDown(int rt, int len) { // 向下更新:如果当前节点有懒惰标记,则更新子节点 if (lazy[rt]) { // 更新左儿子:左儿子区间长度为len - (len>>1) lazy[rt<<1] = lazy[rt]; lazy[rt<<1|1] = lazy[rt]; // 更新左儿子的总价值:材料值乘以区间长度 tree[rt<<1] = lazy[rt] * (len - (len>>1)); tree[rt<<1|1] = lazy[rt] * (len>>1); // 清除当前节点的懒惰标记 lazy[rt] = 0; } } void build(int l, int r, int rt) { // 初始化懒惰标记 lazy[rt] = 0; if (l == r) { // 叶子节点,初始化为1(铜) tree[rt] = 1; return; } int mid = (l + r) >> 1; build(lson); build(rson); pushUp(rt); // 向上更新 } void update(int L, int R, int c, int l, int r, int rt) { // [L,R]为更新区间,c为要设置的材料值 if (L <= l && r <= R) { // 当前区间完全包含在更新区间内 lazy[rt] = c; tree[rt] = c * (r - l + 1); // 更新当前区间的总价值 return; } pushDown(rt, r - l + 1); // 向下更新,传递懒惰标记 int mid = (l + r) >> 1; if (L <= mid) update(L, R, c, lson); if (R > mid) update(L, R, c, rson); pushUp(rt); // 向上更新 } int main() { int T, n, q; scanf("%d", &T); for (int cas = 1; cas <= T; cas++) { scanf("%d", &n); build(1, n, 1); // 建树,初始化 scanf("%d", &q); while (q--) { int a, b, c; scanf("%d%d%d", &a, &b, &c); update(a, b, c, 1, n, 1); // 更新区间[a,b]为材料c } printf("Case %d: The total value of the hook is %d.\n", cas, tree[1]); } return 0; } ``` 注意:题目要求输出格式为“Case X: The total value of the hook is Y.”,其中X是测试用例编号,Y是总价值。 §§相关问题§§ 1. 线段树在区间更新中的应用原理是什么? 2. 除了线段树,还有哪些方法可以解决HDU1698这类问题? 3. 杭电OJ上还有哪些经典的线段树题目推荐? </think>根据您的要求,我将为您解析杭电OJ题目HDU1698《Just a Hook》,这是一道经典的线段树区间更新问题。 ### 题目解析 **题目描述** 初始有一条由 $n$ 段组成的金属钩($1 \leq n \leq 100,000$),每段材质为铜(价值1)。需要执行 $Q$ 次操作($1 \leq Q \leq 100,000$),每次操作将区间 $[a,b]$ 的材质修改为银(价值2)或金(价值3)。最终计算整个钩子的总价值。 **核心算法** 使用**线段树**配合**懒惰标记(Lazy Propagation)** 高效处理区间更新: 1. **线段树结构** - 每个节点存储区间 $[l,r]$ 的总价值和材质标记 - 初始值:所有节点价值为1(铜) 2. **懒惰标记优化** - 更新时若区间完全覆盖,直接修改节点值并打上懒惰标记 - 查询/更新子区间前下传标记,避免重复操作 3. **时间复杂度** $\Theta(Q \log n)$,满足大规模数据要求 ### 参考代码(C++) ```cpp #include <iostream> #include <cstdio> #define lson l, mid, rt<<1 #define rson mid+1, r, rt<<1|1 using namespace std; const int MAXN = 100005; int tree[MAXN<<2]; // 线段树数组 int lazy[MAXN<<2]; // 懒惰标记 // 更新子节点 void pushDown(int rt, int len) { if (lazy[rt]) { lazy[rt<<1] = lazy[rt]; lazy[rt<<1|1] = lazy[rt]; tree[rt<<1] = lazy[rt] * (len - (len>>1)); tree[rt<<1|1] = lazy[rt] * (len>>1); lazy[rt] = 0; } } // 构建线段树 void build(int l, int r, int rt) { lazy[rt] = 0; if (l == r) { tree[rt] = 1; return; } int mid = (l + r) >> 1; build(lson); build(rson); tree[rt] = tree[rt<<1] + tree[rt<<1|1]; } // 区间更新 void update(int L, int R, int val, int l, int r, int rt) { if (L <= l && r <= R) { lazy[rt] = val; tree[rt] = val * (r - l + 1); return; } pushDown(rt, r - l + 1); int mid = (l + r) >> 1; if (L <= mid) update(L, R, val, lson); if (R > mid) update(L, R, val, rson); tree[rt] = tree[rt<<1] + tree[rt<<1|1]; } int main() { int T, n, q; scanf("%d", &T); for (int cas = 1; cas <= T; ++cas) { scanf("%d%d", &n, &q); build(1, n, 1); // 初始化钩子 while (q--) { int a, b, c; scanf("%d%d%d", &a, &b, &c); update(a, b, c, 1, n, 1); //更新区间 } printf("Case %d: The total value of the hook is %d.\n", cas, tree[1]); } return 0; } ``` ### 关键知识点 1. **线段树结构设计** 节点存储区间值和懒惰标记,空间开原始数据4倍 2. **懒惰标记工作流程** ```mermaid graph TD A[区间更新请求] --> B{区间完全覆盖?} B -->|是| C[更新当前节点并打标记] B -->|否| D[下传已有标记] D --> E[递归更新子区间] E --> F[合并子节点值] ``` 3. **边界处理技巧** - 使用位运算加速:`rt<<1` 代替 `2*rt` - 区间中点计算:`mid = (l + r) >> 1` ### 注意事项 1. 多组输入数据需要重置线段树和懒惰标记 2. 输出格式必须严格匹配题目要求(包含`Case X:`前缀) 3. 初始钩子长度为n,不是固定值100,000
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值