[洛谷P3242] [HNOI2015]接水果
洛谷题目链接:[HNOI2015]接水果
题目描述
风见幽香非常喜欢玩一个叫做 osu!的游戏,其中她最喜欢玩的模式就是接水果。由于她已经DT FC 了The big black, 她觉得这个游戏太简单了,于是发明了一个更加难的版本。
首先有一个地图,是一棵由 n 个顶点、n-1 条边组成的树(例如图 1给出的树包含 8 个顶点、7 条边)。
这颗树上有 P 个盘子,每个盘子实际上是一条路径(例如图 1 中顶点 6 到顶点 8 的路径),并且每个盘子还有一个权值。第 i 个盘子就是顶点a_i到顶点b_i的路径(由于是树,所以从a_i到b_i的路径是唯一的),权值为c_i。
接下来依次会有Q个水果掉下来,每个水果本质上也是一条路径,第i 个水果是从顶点 u_i 到顶点v_i 的路径。
幽香每次需要选择一个盘子去接当前的水果:一个盘子能接住一个水果,当且仅当盘子的路径是水果的路径的子路径(例如图1中从 3到7 的路径是从1到8的路径的子路径)。这里规定:从a 到b的路径与从b到 a的路径是同一条路径。
当然为了提高难度,对于第 i 个水果,你需要选择能接住它的所有盘子中,权值第 k_i 小的那个盘子,每个盘子可重复使用(没有使用次数的上限:一个盘子接完一个水果后,后面还可继续接其他水果,只要它是水果路径的子路径)。幽香认为这个游戏很难,你能轻松解决给她看吗?
输入输出格式
输入格式:
第一行三个数 n和P 和Q,表示树的大小和盘子的个数和水果的个数。 接下来n-1 行,每行两个数 a、b,表示树上的a和b 之间有一条边。树中顶点按1到 n标号。 接下来 P 行,每行三个数 a、b、c,表示路径为 a 到 b、权值为 c 的盘子,其中0<=c<=10^9,a不等于b。 接下来Q行,每行三个数 u、v、k,表示路径为 u到 v的水果,其中 u不等于v,你需要选择第 k小的盘子,第k 小一定存在。
输出格式:
对于每个果子,输出一行表示选择的盘子的权值。
输入输出样例
输入样例#1:
10 10 10
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
3 2 217394434
10 7 13022269
6 7 283254485
6 8 333042360
4 6 442139372
8 3 225045590
10 4 922205209
10 8 808296330
9 2 486331361
4 9 551176338
1 8 5
3 8 3
3 8 4
1 8 3
4 8 1
2 3 1
2 3 1
2 3 1
2 4 1
1 4 1
输出样例#1:
442139372
333042360
442139372
283254485
283254485
217394434
217394434
217394434
217394434
217394434
说明
N,P,Q<=40000。
一句话题意:
给你一个树上路径集合\(S\),每条路径有个权值.每次询问一条路径\(p:x\to y\),问他在\(S\)中包含的路径中权值第\(k\)小的是多少.
题解: 我们首先来考虑如何确定路径的包含关系.首先我们需要现将这颗树剖分一下,标记每个点的\(dfs\)序,用\(L[x]\)表示\(x\)的\(dfs\)序,\(R[x]\)表示\(L[x]+size[x]-1\).
然后我们可以将一条树上的路径\((u,v)\)看作是一个二元组\((u,v)\),将这个二元组的\(dfs\)序映射到二维平面上,也就是一个点\(L[u],L[v]\).这样我们就可以比较方便的表示出一条树上路径.
接着我们分类讨论一下.假设路径\((u,v)\)是\((x,y)\)的子路径,且\(deep[u]<deep[v]\),则有:
- 若\(lca(u,v)==u\)
设\(z\)是\(u\to v\)上的第一个点
那么就要求路径\(p\)满足一个节点在\(v\)子树内,一个节点在\(z\)子树外
也就是说这次修改操作可以影响到的范围是\(\{(1,L[z]-1),(L[v],R[v])\}\)和\(\{(R[z]+1),(L[v],R[v])\}\)
而这个影响的范围正好对应了二维平面上的一个矩形.
所以对于查询的一条路径可以直接在二维平面上单点查询.
- 若\(lca(u,v)!=u\)
则这次修改可以影响到的范围是\(\{(L[u],R[u]),(L[v],R[v])\}\),同样是一个矩形范围.
经过上面的分析,我们发现,要统计修改所造成的贡献,只需要统计一个二维前缀和就可以了,所以可以采用树状数组来修改查询.
然后我们会发现,如果二维修改复杂度太大了,过不了.所以这时候我们需要使用扫描线 优化一下这个矩阵修改的过程,也就是将一个矩形的修改变成两条线段的修改,这样复杂度就降低了一个维度.
最后我们来考虑如何回答询问.因为修改操作对询问都可以产生贡献,而每个修改都是独立的,并且又要求我们求出一个询问的第\(k\)小,所以我们可以采用整体二分.
我们先将所有修改操作改成一根根的扫描线,然后将修改操作的扫描线按照\(x\)轴顺序排个序.查询操作也需要按照\(x\)轴坐标排个序.因为我这里是直接将扫描线加入了整体二分的过程,所以要保证在处理询问的时候只有比当前询问\(x\)轴坐标小的对这次询问作贡献.
然后在整体二分的过程中枚举当前区间中所有比当前查询的\(x\)轴坐标小的修改操作加入树状数组中,因为已经将\(x\)轴进行了排序,所以在树状数组中只需要查询\(y\)轴的坐标(排序保证了当前查询状态是最新的).
然后有一个要注意的重要的细节:因为对于一次修改操作,修改的这条路径是无向的.所以修改的矩形可以算成两个,这时候如果只修改一个就涉及到了\(x\)轴\(y\)轴具体要修改哪一个的问题.比如说修改一个区间\(\{(L[u],R[u]),(L[v],R[v])\}\),既可以是前面的范围作为\(x\)轴上的范围(\(\{(L[u],R[u]),(L[v],R[v])\}\)),也可以是后面的那个作为\(x\)轴上的范围(\(\{(L[v],R[v]),(L[u],R[u])\}\)).所以这里我默认\(x\)轴上修改的那个范围是\(dfs\)序小的,同时查询也将\(x\)轴默认为小的,这样就不会出现路径查询的时候有子路径没有计入答案的问题了.
代码比较长,调了很久(至今调过最久的代码,上一个是维护数列),细节部分可以再好好想想.
#include<bits/stdc++.h>
using namespace std;
const int N = 40000+5;
int n, m, q, c[N], ans[N], cntv = 0, value[N], now[N];
int ecnt = 0, last[N], tot;
int idx = 0, size[N], top[N], L[N], R[N], dep[N], son[N], fa[N];
struct edge{ int to, nex; }e[N*2];
struct fruits{ int x, y, k, id; }o[N], tq1[N], tq2[N];
struct Updates{ int x, d, u, k, val; }b[N*4], tv1[N*4], tv2[N*4];
bool cmpx(Updates a, Updates b){ return a.x < b.x; }
bool cmpxx(fruits a, fruits b){ return a.x < b.x; }
int gi(){
int ans = 0, f = 1; char i = getchar();
while(i<'0' || i>'9'){ if(i == '-') f = -1; i = getchar(); }
while(i>='0' && i<='9') ans = ans*10+i-'0', i = getchar();
return ans * f;
}
void add(int x, int y){ e[++ecnt].to = y, e[ecnt].nex = last[x], last[x] = ecnt; }
int lowbit(int x){ return x&-x; }
void update(int x, int val){ for(;x<=n;x+=lowbit(x)) c[x] += val; }
int query(int x){
int res = 0;
for(;x;x-=lowbit(x)) res += c[x];
return res;
}
void dfs1(int x, int f, int deep){
dep[x] = deep, fa[x] = f, size[x] = 1;
for(int to, i=last[x];i;i=e[i].nex){
to = e[i].to;
if(to == f) continue;
dfs1(to, x, deep+1), size[x] += size[to];
if(size[to] > size[son[x]]) son[x] = to;
}
}
void dfs2(int x, int tp){
L[x] = ++idx, top[x] = tp;
if(son[x]) dfs2(son[x], tp);
for(int i=last[x];i;i=e[i].nex)
if(e[i].to != son[x] && e[i].to != fa[x]) dfs2(e[i].to, e[i].to);
R[x] = idx;
}
int lcason(int a, int b){
while(top[a] != top[b]){
if(fa[top[a]] == b) return top[a];
a = fa[top[a]];
}
return son[b];
}
void solve(int vl, int vr, int l, int r, int ql, int qr){
if(ql > qr) return;
if(vl == vr){
for(int i=ql;i<=qr;i++) ans[o[i].id] = value[vl];
return;
}
int mid = (vl+vr>>1), cntv1 = 0, cntq1 = 0, cntv2 = 0, cntq2 = 0;
int lenv = l-1, lenq = ql-1, sum, pos = l;
for(int i=ql;i<=qr;i++){ // i stands for queries
for(;pos <= r && b[pos].x <= o[i].x; pos++){
if(b[pos].val <= value[mid]){
tv1[++cntv1] = b[pos];
update(b[pos].d, b[pos].k);
update(b[pos].u+1, -b[pos].k);
} else tv2[++cntv2] = b[pos];
}
sum = query(o[i].y);
if(now[o[i].id]+sum >= o[i].k) tq1[++cntq1] = o[i];
else now[o[i].id] += sum, tq2[++cntq2] = o[i];
}
for(; pos <= r; pos++){
if(b[pos].val <= value[mid]){
tv1[++cntv1] = b[pos];
update(b[pos].d, b[pos].k);
update(b[pos].u+1, -b[pos].k);
} else tv2[++cntv2] = b[pos];
}
for(int i=l;i<=r;i++)
if(b[i].val <= value[mid])
update(b[i].d, -b[i].k), update(b[i].u+1, b[i].k);
for(int i=1;i<=cntv1;i++) b[++lenv] = tv1[i];
for(int i=1;i<=cntv2;i++) b[++lenv] = tv2[i];
for(int i=1;i<=cntq1;i++) o[++lenq] = tq1[i];
for(int i=1;i<=cntq2;i++) o[++lenq] = tq2[i];
solve(vl, mid, l, l+cntv1-1, ql, ql+cntq1-1);
solve(mid+1, vr, l+cntv1, r, ql+cntq1, qr);
}
int main(){
int x, y, z, val; n = gi(), m = gi(), q = gi();
for(int i=1;i<n;i++) x = gi(), y = gi(), add(x, y), add(y, x);
dfs1(1, -1, 1), dfs2(1, 1);
for(int i=1;i<=m;i++){
x = gi(), y = gi(), value[i] = gi();
if(dep[x] > dep[y]) swap(x, y); // dep[x] <= dep[y]
if(L[x] <= L[y] && L[y] <= R[x]){
z = lcason(y, x);
if(L[z] > 1){
if(L[z]-1 < L[y]){
b[++cntv] = (Updates){ 1, L[y], R[y], 1, value[i] };
b[++cntv] = (Updates){ L[z], L[y], R[y], -1, value[i] };
} else {
b[++cntv] = (Updates){ L[y], 1, L[z]-1, 1, value[i] };
b[++cntv] = (Updates){ R[y]+1, 1, L[z]-1, -1, value[i] };
}
}
if(R[z]+1 <= n){
b[++cntv] = (Updates){ L[y], R[z]+1, n, 1, value[i] };
b[++cntv] = (Updates){ R[y]+1, R[z]+1, n, -1, value[i] };
}
} else {
if(L[x] < L[y]){
b[++cntv] = (Updates){ L[x], L[y], R[y], 1, value[i] };
b[++cntv] = (Updates){ R[x]+1, L[y], R[y], -1, value[i] };
} else {
b[++cntv] = (Updates){ L[y], L[x], R[x], 1, value[i] };
b[++cntv] = (Updates){ R[y]+1, L[x], R[x], -1, value[i] };
}
}
}
sort(b+1, b+cntv+1, cmpx), sort(value+1, value+m+1);
tot = unique(value+1, value+m+1)-value-1;
for(int i=1;i<=q;i++){
x = gi(), y = gi(), val = gi();
if(L[x] > L[y]) swap(x, y);
o[i] = (fruits){ L[x], L[y], val, i };
}
sort(o+1, o+q+1, cmpxx);
solve(1, tot, 1, cntv, 1, q);
for(int i=1;i<=q;i++) cout << ans[i] << endl;
return 0;
}
[洛谷P3242] [HNOI2015]接水果的更多相关文章
- 洛谷 P3242 [HNOI2015]接水果 解题报告
P3242 [HNOI2015]接水果 题目描述 风见幽香非常喜欢玩一个叫做 \(osu!\) 的游戏,其中她最喜欢玩的模式就是接水果.由于她已经\(DT\) \(FC\) 了\(\tt{The\ b ...
- ●洛谷P3242 [HNOI2015]接水果
题链: https://www.luogu.org/problemnew/show/P3242 题解: 整体二分,扫描线+树状数组. 详细的题解:http://blog.csdn.net/thy_as ...
- 洛谷P3242 接水果 [HNOI2015] 整体二分
正解:整体二分+树状数组 解题报告: 传送门! 题目还是大概解释下?虽然其实是看得懂的来着,,, 大概就是说给一棵树.给定一些询问,每个询问都是说在两个点之间的路径上的子路径的第k大是什么 然后看到这 ...
- 洛谷P3242 接水果
关于矩形与点其实有两种关系. 一种是每个矩形包含多少点.一种是每个点被多少矩形包含. 解:因为可以离线所以直接套整体二分.关键是考虑如何能够被覆盖. 我一开始都是想的树上操作...其实是转化成DFS序 ...
- [洛谷 P3239] [HNOI2015]亚瑟王
[HNOI2015]亚瑟王 题目描述 小 K 不慎被 LL 邪教洗脑了,洗脑程度深到他甚至想要从亚瑟王邪教中脱坑.他决定,在脱坑之前,最后再来打一盘亚瑟王.既然是最后一战,就一定要打得漂亮.众所周知, ...
- 洛谷 P3241 [HNOI2015]开店 解题报告
P3241 [HNOI2015]开店 题目描述 风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学.最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱. 这样的想法当然非 ...
- 洛谷P3244 [HNOI2015]落忆枫音
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> #in ...
- luogu P3242 [HNOI2015]接水果
传送门 其实这题难点在于处理路径包含关系 先求出树的dfn序,现在假设路径\(xy\)包含\(uv(dfn_x<dfn_y,dfn_u<dfn_v)\) 如果\(lca(u,v)!=u\) ...
- 洛谷P3243 [HNOI2015]菜肴制作 拓扑排序+贪心
正解:拓扑排序 解题报告: 传送门! 首先看到它这个约束就应该要想到拓扑排序辣QwQ 首先想到的应该是用优先队列代替队列,按照节点编号排序 然后也很容易被hack:<5,1> 正解应为5, ...
随机推荐
- 使用Promise链式调用解决多个异步回调的问题
使用Promise链式调用解决多个异步回调的问题 比如我们平常经常遇到的一种情况: 网站中需要先获取用户名,然后再根据用户名去获取用户信息.这里获取用户名getUserName()和获取用户信息get ...
- 衡量失业:失业率(Unemployment Rate)
定义 劳动力 = 就业人数 + 失业人数 失业率 = (失业人数 / 劳动力) * % 劳动力参与率 = (劳动力 / 成年人口) * %
- 解决python中文编码错误问题
对于初学者而言,编码问题或许还没有没重视起来,但是编码问题是中文开发者必须面对的.今天来看下python开发中如何解决编码问题.注意:本篇讲的是最常见的一种编码问题,其他编码问题,如json函数引起的 ...
- Coursera-Note: Internet History, Technology and Secure (1st week to 9th week)
目录 Coursera-Note: Internet History, Technology and Secure 第一周 第二周 数据交换: Packet switching技术: 第三周 创造ht ...
- java — JVM调优
数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本身, ...
- HDU 2124 Repair the Wall
http://acm.hdu.edu.cn/showproblem.php?pid=2124 Problem Description Long time ago , Kitty lived in a ...
- C跟C++
工作以来几乎就只写C,下周开始的新工作可能就要开始写C++啦~ C: C++: 1.template 2.多态.重载.继承 3.虚函数 C++内存布局 看两张耗子的图片就清楚了.其中虚函数表一般放在数 ...
- Delphi中Self和Sender的区别
在事件处理程序参数表中,至少含有一个参数Sender,它代表触发事件处理程序的构件,如在上例中,Sender就指Button2,有了Sender参数,可以使多个构件共用相同的事件处理程序,如下例: ...
- MATLAB strcmp
比较两个输入字符串是否相等 c = strcmp(str1,str2)比较字符串 str1 与 str2 ,若完全相等则返回 1 ,不相等返回 0 str1 = 'hello'; str2 = 'he ...
- Oracle-RAC原理
Oracle-RAC原理 来源 https://blog.csdn.net/qq_34556414/article/details/79001267 单点数据库 VS RAC 单节点数据库,如果实例宕 ...