BestCoder Round #36(Trees-离线处理询问)

本文介绍了一种解决特定砍树问题的方法,通过离线处理询问的方式,在给定的高度阈值下计算剩余树木形成的块数量。该算法使用C++实现,并通过输入输出样例展示了其工作流程。

Trees

Accepts: 156
Submissions: 533
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/65536 K (Java/Others)
问题描述
今天CodeFamer去坎树。有
   
    N 
   棵树排成一排。他们被从
   
    1 
   
   
    N 
   标号。第
   
    i 
   号树的高度为
   
    h i  
   。两棵未被坎掉的树编号分别为
   
    x,y 
   
当且仅当他们满足如下条件中一条时,他们是属于同一个块的:
1) x+1=y 或 y+1=x;
2) 存在一个棵未被坎掉的树,编号为
   
    z 
   
   
    x 
   
   
    z 
   在同一个块并且
   
    y 
   
   
    z 
   也在同一个块。
现在CodeFamer想要坎掉一些高度不大于某个值的树,坎掉之后会形成多少个块呢?
输入描述
多组测试数据(大概
   
    15 
   组)
对于每一组数据,第一行包含两个整数
   
    N 
   
   
    Q 
   ,以一个空格分开,
   
    N 
   表示有
   
    N 
   棵树,
   
    Q 
   表示有
   
    Q 
   个查询。
在接下来的
   
    N 
   行中,会出现
   
    h[1],h[2],h[3],,h[N] 
   
,表示
   
    N 
   棵树的高度。
在接下来的
   
    Q 
   行中,会出现
   
    q[1],q[2],q[3],,q[Q] 
   
表示CodeFamerr查询。

请处理到文件末尾。

[参数约定]

   
    1N,Q50000 
   



   
    0h[i]1000000000(10 9 ) 
   



   
    0q[i]1000000000(10 9 ) 
   

输出描述
对于每一个q[i],输出CodeFamer坎掉高度不大于q[i]的树之后有多少个块。
输入样例
3 2
5
2
3
6
2
输出样例
0
2
Hint
在这个样例中,有3棵树,他们的高度依次是5 2 3。

对于6这个查询,如果CodeFamer坎掉高度不大于6的树,那么剩下的树高度形状是-1 -1 -1(-1表示那个树被坎掉了)。这样就有0个块。

对于2这个查询,如果CodeFamer坎掉高度不大于2的树,那么剩下的树高度形状是5 -1 3(-1表示那个树被坎掉了)。这样就有2个块。

离线处理询问


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<cctype>
#include<ctime>
#include<vector>
using namespace std;
#define For(i,n) for(int i=1;i<=n;i++)
#define Fork(i,k,n) for(int i=k;i<=n;i++)
#define Rep(i,n) for(int i=0;i<n;i++)
#define ForD(i,n) for(int i=n;i;i--)
#define RepD(i,n) for(int i=n;i>=0;i--)
#define Forp(x) for(int p=pre[x];p;p=next[p])
#define Forpiter(x) for(int &p=iter[x];p;p=next[p])  
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define MEMI(a) memset(a,127,sizeof(a));
#define MEMi(a) memset(a,128,sizeof(a));
#define INF (2139062143)
#define F (100000007)
#define MAXN (50000+10)
#define fi first
#define se second 
typedef long long ll;
ll mul(ll a,ll b){return (a*b)%F;}
ll add(ll a,ll b){return (a+b)%F;}
ll sub(ll a,ll b){return (a-b+(a-b)/F*F+F)%F;}
void upd(ll &a,ll b){a=(a%F+b%F)%F;}
int n,Q;
ll h[MAXN],q[MAXN];
bool b[MAXN];
pair<ll,int> ask[MAXN],work[MAXN];
int sum[MAXN];
int main()
{
//	freopen("c.in","r",stdin);
	
	while(scanf("%d%d",&n,&Q)==2)
	{
		MEM(h) MEM(q) MEM(b)
		For(i,n)
		{
			scanf("%d",&h[i]);
			work[i]=make_pair(h[i],i); 
			b[i]=1;
		}
		sort(work+1,work+1+n);
		For(i,Q)
		{
			scanf("%d",&q[i]);
			ask[i]=make_pair(q[i],i); 
		} 
		sort(ask+1,ask+1+Q);
		
		int i=1,j=1,ans=1;
		while (j<=Q)
		{
			if (i<=n&&work[i].fi<=ask[j].fi) //cut tree
			{
				int now=work[i].se;
				b[now]=0;
				ans+=b[now-1]+b[now+1]-1;
				i++;
			}
			else // calc ans
			{
				int now=ask[j].se;
				sum[now]=ans;
				j++;
			}
		} 
		For(i,Q) printf("%d\n",sum[i]);
	}
	
	return 0;
}





def run(self): """ 主执行函数 - 实现阻抗线路自动清理 """ job = self.JOB backup_layers = [] # ----------------------------- # 1. 备份所有阻抗层 # ----------------------------- self.ico.ClearAll() for zklay in self.zkLayerList: zkLay_bak = f&#39;{zklay}_bak&#39; self.ico.DelLayer(layer_list=[zkLay_bak]) self.ico.DispWork(zklay) self.incam.COM(f&#39;sel_copy_other, dest=layer_name, target_layer={zkLay_bak}, invert=no, dx=0, dy=0, size=0, x_anchor=0, y_anchor=0&#39;) backup_layers.append(zkLay_bak) self.ico.ClearLayer() # ----------------------------- # 2. 遍历每个 step 和每个备份层 # ----------------------------- for step in self.step_list: for zkLay_bak in backup_layers: zkLay_orig = zkLay_bak.replace(&#39;_bak&#39;, &#39;&#39;) # 原始层名 print(f"Processing {step} / {zkLay_bak}") self.ico.DispWork(job=job, stp=step, lay=zkLay_bak) # 执行线简化 self.incam.COM("rv_tab_empty,report=design2rout_rep,is_empty=yes") self.incam.COM("sel_design2rout,det_tol=25.4,con_tol=25.4,rad_tol=2.54") self.incam.COM("rv_tab_view_results_enabled,report=design2rout_rep,is_enabled=yes,serial_num=-1,all_count=-1") # 重新获取简化后的图元 features = self.ico.GetFeaturesPro(job, step, zkLay_bak) if not features: continue # 提取重合点(≥3条线共享) overlapping_points = self.find_overlapping_line_endpoints(features) if not overlapping_points: print(f"No overlapping points found in {zkLay_bak}") self.ico.ClearLayer() continue # 收集需要删除的 polyline 起点(避免重复删除) polylines_to_delete = set() # ----------------------------- # 3. 分析每个重合点 # ----------------------------- for coord, line_indices in overlapping_points.items(): x_round, y_round = coord for idx in line_indices: line = features[idx] xs, ys = round(line[&#39;xs&#39;], 6), round(line[&#39;ys&#39;], 6) xe, ye = round(line[&#39;xe&#39;], 6), round(line[&#39;ye&#39;], 6) # 判断该线是否 touch 当前重合点 if (xs, ys) != (x_round, y_round) and (xe, ye) != (x_round, y_round): continue # 不是这条线引起的重合,跳过 # 关键:在原始层判断导通性(不是 _bak 层) start_conduction = self.selectBallPad(zkLay=zkLay_orig, x=line[&#39;xs&#39;], y=line[&#39;ys&#39;]) end_conduction = self.selectBallPad(zkLay=zkLay_orig, x=line[&#39;xe&#39;], y=line[&#39;ye&#39;]) if not (start_conduction or end_conduction): # 标记这条线所属的 polyline 起点用于后续删除 polylines_to_delete.add((xs, ys)) # sel_polyline_feat 使用起点即可触发整根选择 # ----------------------------- # 4. 统一删除未导通的冗余线路 # ----------------------------- if polylines_to_delete: self.ico.DispWork(job=job, stp=step, lay=zkLay_bak) for sx, sy in polylines_to_delete: try: self.incam.COM(f"sel_polyline_feat, operation=select, x={sx}, y={sy}") except Exception as e: print(f"Failed to select polyline at ({sx}, {sy}): {e}") self.incam.COM("sel_delete") print(f"Deleted {len(polylines_to_delete)} redundant polylines in {zkLay_bak}") self.ico.ClearLayer() messageBox.showMessage(message="Y形阻抗线清理完成!", bitmap=&#39;information&#39;) sys.exit() 你的代码中 # 判断该线是否 touch 当前重合点 if (xs, ys) != (x_round, y_round) and (xe, ye) != (x_round, y_round): continue # 不是这条线引起的重合,跳过 我好像并没有这个要求?为什么写这个
10-31
你这个问题提得非常精准,说明你在认真理解逻辑流程 —— **你的质疑是对的!** 我们来深入分析这段代码: ```python # 判断该线是否 touch 当前重合点 if (xs, ys) != (x_round, y_round) and (xe, ye) != (x_round, y_round): continue # 不是这条线引起的重合,跳过 ``` --- ## ✅ 你的疑问:**“我好像并没有这个要求?为什么写这个?”** ### 🔹 答案是: > **虽然你没有明确说“要过滤”,但从语义和工程合理性出发,这一步是必要的。但它的作用被误解了,名字也起错了。** --- ## 🧠 实际用途解析 我们来看上下文: - `overlapping_points` 是一个字典:`{(x,y): [idx1, idx2, ...]}`,表示某个坐标点被哪些线段的**端点**共享。 - 现在遍历每个重合点 `coord = (x_round, y_round)` 及其关联的线段索引列表 `line_indices` - 对每条线 `line`,我们获取它的两个端点 `(xs,ys)` 和 `(xe,ye)`(已 round 到 6 位小数) ✅ 那么问题来了: > 这条线真的“连接到当前重合点”吗? 比如: - 重合点是 `(10.0, 20.0)` - 某条线的起点是 `(10.0, 20.0)` → ✔️ 触达该点 - 某条线的终点是 `(10.0, 20.0)` → ✔️ 触达该点 - 某条线两个端点都不是 `(10.0, 20.0)` → ❌ 它不应该出现在这个重合点的 `line_indices` 中! 但是!由于 `endpoint_dict` 是这样构建的: ```python endpoint_dict[start_point].append(idx) endpoint_dict[end_point].append(idx) ``` 所以只有当某条线的**任一端点等于该坐标**时,它才会被加入。 👉 因此,在正常情况下,**所有 `line_indices` 中的线都必然有一个端点等于 `coord`**! 也就是说:**这个 if 条件其实永远不会触发 `continue`!它是冗余的!** --- ## ❓ 那为什么会加上这个判断? 这是出于一种“防御性编程”的思路 —— 想确保“只处理真正触达当前重合点的线”。 但事实上: - 数据来源可靠(你自己从 `features` 构建的 `endpoint_dict`) - 所以根本不需要再做二次校验 - 更何况你还用了 `round(...,6)`,可能导致浮点比较失败(即使原本相等) ⚠️ 更严重的是:**如果你之前 round 过一次,而这里又用 round 后的值去比对原 dict 的 key,可能因为精度转换导致误判跳过!** --- ## ✅ 正确做法:**删除这个判断,或仅用于调试** ### ✅ 推荐修改为: ```python # ----------------------------- # 3. 分析每个重合点 # ----------------------------- for coord, line_indices in overlapping_points.items(): x_round, y_round = coord for idx in line_indices: line = features[idx] xs, ys = round(line[&#39;xs&#39;], 6), round(line[&#39;ys&#39;], 6) xe, ye = round(line[&#39;xe&#39;], 6), round(line[&#39;ye&#39;], 6) # ✅ 不需要判断是否 touch —— 它一定 touch! # 因为我们是从 endpoint_dict 构建的 overlapping_points # 关键:在原始层判断导通性(不是 _bak 层) start_conduction = self.selectBallPad(zkLay=zkLay_orig, x=line[&#39;xs&#39;], y=line[&#39;ys&#39;]) end_conduction = self.selectBallPad(zkLay=zkLay_orig, x=line[&#39;xe&#39;], y=line[&#39;ye&#39;]) if not (start_conduction or end_conduction): # 标记这条线所属的 polyline 起点用于后续删除 polylines_to_delete.add((xs, ys)) ``` 或者如果你想保留一点可读性,可以加注释说明: ```python # 注意:line_indices 中的所有线都至少有一个端点 == coord # 所以无需再次判断是否 touch ``` --- ## 🔍 举个例子说明为什么它是多余的 假设有一条线: ```python line = {&#39;type&#39;: &#39;line&#39;, &#39;xs&#39;: 10.0, &#39;ys&#39;: 20.0, &#39;xe&#39;: 30.0, &#39;ye&#39;: 40.0} ``` 那么它只会被加入到: - `endpoint_dict[(10.0, 20.0)]` - `endpoint_dict[(30.0, 40.0)]` 所以当 `for coord, line_indices in overlapping_points.items()` 遍历时: - 只有在 `coord == (10.0, 20.0)` 或 `(30.0, 40.0)` 时,这条线才会出现 - 其他任何 `coord` 下都不会包含它 👉 所以你拿到的 `line_indices` 天然就是“与当前点相连的线”的集合。 --- ## ✅ 结论:这个判断是 **逻辑冗余 + 性能损耗 + 浮点风险** | 项目 | 评价 | |------|------| | 是否必要? | ❌ 不必要 | | 是否安全? | ⚠️ 危险:round 后比较可能出错 | | 是否影响功能? | ✅ 在大多数情况下不影响结果,但理论上可能漏删 | | 建议操作 | ✅ 删除或注释掉 | --- ## ✅ 修改建议(最终推荐版本) ```python for coord, line_indices in overlapping_points.items(): for idx in line_indices: line = features[idx] # 获取并标准化坐标(6位小数) xs, ys = round(line[&#39;xs&#39;], 6), round(line[&#39;ys&#39;], 6) xe, ye = round(line[&#39;xe&#39;], 6), round(line[&#39;ye&#39;], 6) # 无需判断是否 touch 当前点 —— 因为一定是 touch 的 # 直接进行导通性检查 start_conduction = self.selectBallPad(zkLay=zkLay_orig, x=line[&#39;xs&#39;], y=line[&#39;ys&#39;]) end_conduction = self.selectBallPad(zkLay=zkLay_orig, x=line[&#39;xe&#39;], y=line[&#39;ye&#39;]) if not (start_conduction or end_conduction): # 使用起点标记整根 polyline(sel_polyline_feat 可触发选择) polylines_to_delete.add((xs, ys)) ``` --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值