砍树

砍树(2)

  小A是小B家的园丁。小B的家里有n棵树,第i棵树的横坐标为i。一天,小B交给小A一个任务,让他降低自己家中的某些树木的高度。这个任务对小A来说十分简单,因为他有一把极其锋利的斧头和一门独门砍树秘籍,能够轻易地砍断任何参天大树。小A的砍树方法有3种,都是沿着一条y=kx+b的直线砍一段区间的树,相同的方法k值相同。只用了一个下午,小A就完成了小B的任务。第二天,小B来视察小A的任务完成情况。小B想知道小A是否真的用心砍树,于是提出了q个询问,每次询问一段区间中最低的树的高度。小A当然是不会记住树木的砍伐情况的,他只知道自己按什么顺序,使用了什么方法,砍了哪个连续区间的树,而且区间都是互不包含的。现在小A想请你帮帮他,回答小B的询问。

输入格式:

第一行三个整数k1,k2,k3表示小A三种砍树方法的斜率值;
第二行一个数n,表示一共有n棵树;
第三行n个数hi,分别表示n棵树的高度;
第四行一个数m,表示小A一共进行了m次操作;
接下来m行,每行四个数L,R,p,b,表示用第p种方法,即用y=kp+b的直线砍[L,R]区间的树;
接下来一行一个数q,表示小B的询问数;
接下来q行,每行两个数L,R,表示询问[L,R]区间中最低的树的高度。

输出格式:

一共q行,每行一个数h表示对应的回答。

样例输入:

1 0 -1
4
10 30 20 1
2
3 4 2 5
1 3 3 10
2
1 2
2 3

样例输出:

8
5

数据范围:

n<=1000000,m<=500000

时间限制:

3s

空间限制:

64M

提示:

如下图,红色即为树的剩余部分。
1.jpg
本题是考代码能力的线段树,砍树和区间求最值都要求用logn维护
lazy标记略难,用三个数组表示三个kx+b的常数的最小值,这样到查询之前统一处理就可以根据以下性质进行推导,先递归他的儿子再取两个儿子中的小,边界是仅一个,此时根据上面传下来的函数计算即可。
区间求最小仅是传懒标记比较特殊,其他也没什么。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using  namespace  std;
#define PER(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
int  k[3];
int  val[2100005];
int  b[2100005][3];
int  h[1100005];
int  l[2100005];
int  r[2100005];
int  n,m,q;
int  lb,rb,p,bb;
const  int  INF=100000000;
//建树操作
int  build( int  ll, int  rr, int  no)
{
     int  mid=(ll+rr)>>1;l[no]=ll;r[no]=rr;
     b[no][0]=b[no][1]=b[no][2]=INF;//0、1、2是三种type
     if (ll==rr){ return  val[no]=h[ll];}
     int  lc=no<<1,rc=lc+1;
     return  val[no]=min(build(ll,mid,lc),build(mid+1,rr,rc));
}

void  add( int  no, int  x, int  y, int  p, int  bb)
{
     if (l[no]==x&&r[no]==y){b[no][p]=min(b[no][p],bb); return ;}//添加时记录懒标记
     int  mid=(l[no]+r[no]) >> 1,lc=no*2,rc=lc+1;
     if (x>mid) {add(rc,x,y,p,bb); return ;} if (y<=mid) {add(lc,x,y,p,bb); return ;}
     add(lc,x,mid,p,bb);add(rc,mid+1,y,p,bb);
     return ;
}
void  dfs( int  no)
{
     int  lc=no*2,rc=lc+1;
     if (l[no]!=r[no])
     {
     PER(i,0,2){
         int  wa=b[no][i];
         b[lc][i]=min(b[lc][i],wa);
         b[rc][i]=min(b[rc][i],wa);
     }
         dfs(lc);dfs(rc);
         val[no]=min(val[lc],val[no]);
         val[no]=min(val[rc],val[no]);
     }
     else
     {
         PER(i,0,2)
         {
             int  wa=b[no][i];
             if (wa!=INF) //错误瓶颈判断该函数是否存在,数据结果可能很大,要么把INF设得很大
             val[no]=min(val[no],k[i]*r[no]+wa);
         }
     }
}
int  doit( int  no, int  x, int  y)//区间查询,线段树版
{
     if (l[no]==x&&r[no]==y)  return  val[no];
     int  mid=(l[no]+r[no])>>1; int  lc=no*2,rc=lc+1;
     if (y<=mid)  return  doit(lc,x,y);
     if (x>mid)  return  doit(rc,x,y);
     return  min(doit(lc,x,mid),doit(rc,mid+1,y));
}
int  main()
{
     scanf ( "%d%d%d" ,&k[0],&k[1],&k[2]);
     scanf ( "%d" ,&n);
     PER(i,1,n)  scanf ( "%d" ,&h[i]);
     build(1,n,1);
     scanf ( "%d" ,&m);
     while (m--)
     {
         scanf ( "%d%d%d%d" ,&lb,&rb,&p,&bb);
         add(1,lb,rb,p-1,bb);
     }
     scanf ( "%d" ,&q);
     dfs(1);
     while (q--)
     {
         scanf ( "%d%d" ,&lb,&rb);
         printf ( "%d\n" ,doit(1,lb,rb));
     }
}


### 砍树算法的概念与实现 砍树算法通常指的是在图论中处理最小生成树(Minimum Spanning Tree, MST)的相关问题。它是一种用于解决连通加权无向图的优化问题的经典方法,目标是从给定的图中找到一棵包含所有顶点且总权重最小的子树。 #### 1. 数据结构的选择 为了实现砍树算法,首先需要选择合适的存储方式来表示图。常见的两种存储方式分别是邻接矩阵和邻接表[^3]。对于稠密图而言,邻接矩阵更为高效;而对于稀疏图,则推荐使用邻接表以节省空间开销。 以下是基于邻接矩阵的图定义代码示例: ```c #define MVNum 100 // 假设最大顶点数为100 #define INF INT_MAX // 表示极大值无穷大 typedef char VerTexType; // 设置顶点的数据类型为字符型 typedef int ArcType; // 设置边的权值类型为整型 typedef struct { VerTexType vexs[MVNum]; // 存储顶点信息的一维数组 ArcType arcs[MVNum][MVNum]; // 邻接矩阵 int vexnum, arcnum; // 图的当前顶点数和弧的数量 } AMGraph; ``` #### 2. 实现砍树算法的具体思路 砍树算法主要包括 Prim 和 Kruskal 两种主要方法: - **Prim 算法**:适用于密集图,通过逐步扩展已有的部分生成树直到覆盖整个图的所有节点。 - **Kruskal 算法**:适合于稀疏图,按照边的权重从小到大依次加入集合,同时利用并查集防止形成环路。 下面展示的是 Prim 算法的一个简单实现版本: ```c #include <stdio.h> #include <limits.h> void MinSpanTree_Prim(AMGraph G) { int adjvex[G.vexnum], lowcost[G.vexnum]; for (int i = 0; i < G.vexnum; ++i) { lowcost[i] = G.arcs[0][i]; // 初始化辅助数组lowcost[] adjvex[i] = 0; // 记录各顶点对应的前驱顶点编号,默认初始状态都指向第一个顶点 } lowcost[0] = 0; for (int j = 1; j < G.vexnum; ++j) { int min = INF, k = -1; // 寻找下一个距离最近的未访问过的顶点k for (int m = 0; m < G.vexnum; ++m) { if ((lowcost[m] != 0) && (lowcost[m] < min)) { min = lowcost[m]; k = m; } } printf("Edge (%c-%c), Cost=%d\n", G.vexs[adjvex[k]], G.vexs[k], lowcost[k]); lowcost[k] = 0; // 更新其他顶点的距离 for (int n = 0; n < G.vexnum; ++n) { if (((G.arcs[k][n]) < lowcost[n]) && (lowcost[n] != 0)) { lowcost[n] = G.arcs[k][n]; adjvex[n] = k; } } } } ``` 此函数实现了 Prim 算法的核心逻辑,并打印每一步所选中的边及其代价[^4]。 #### 3. 应用场景分析 砍树算法广泛应用于网络设计领域,比如铺设电缆线路、构建通信网络等实际工程问题当中。这些场合往往要求尽可能降低材料成本的同时保证系统的连通性和稳定性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值