zoukankan      html  css  js  c++  java
  • [洛谷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;
    }
    
  • 相关阅读:
    【随手记】常用16进制代表的容量或位置
    精通css——position属性
    Ubuntu安装GitLab
    Linux内核
    分布式(一)——分布式系统的特性
    【树莓派】入门
    Intel CPU发展历史
    C++读mnist数据
    实验代码一:用来链表实现单向图
    Hadoop配置+centos6.5
  • 原文地址:https://www.cnblogs.com/BCOI/p/9627467.html
Copyright © 2011-2022 走看看