[洛谷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 o 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]),则有:

  1. (lca(u,v)==u)

(z)(u o v)上的第一个点
那么就要求路径(p)满足一个节点在(v)子树内,一个节点在(z)子树外
也就是说这次修改操作可以影响到的范围是({(1,L[z]-1),(L[v],R[v])})({(R[z]+1),(L[v],R[v])})
而这个影响的范围正好对应了二维平面上的一个矩形.
所以对于查询的一条路径可以直接在二维平面上单点查询.

  1. (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;
}
原文地址:https://www.cnblogs.com/BCOI/p/9627467.html