Description
风见幽香非常喜欢玩一个叫做 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 小的那个盘子,每个盘子可重复使用(没有使用次数
的上限:一个盘子接完一个水果后,后面还可继续接其他水果,只要它是水
果路径的子路径)。幽香认为这个游戏很难,你能轻松解决给她看吗?
Input
第一行三个数 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 小一定存在。
Output
对于每个果子,输出一行表示选择的盘子的权值。
Sample Input
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
Sample Output
442139372
333042360
442139372
283254485
283254485
217394434
217394434
217394434
217394434
217394434
HINT
N,P,Q<=40000。
Source
Solution
本题简化题意:n个结点的树,给定m条带权值的路径(端点不重合),q个询问,每个询问给定一条路径,查询m条路径中所有它的子路径的权值第k小。
首先如果两个路径有包含关系,那么是可以用dfs序的区间表示的,具体看代码,所以我们可以把集合中的两个dfs序区间看成二维平面上的一个矩形,然后询问看成一个点,这样题意就转化成了包含一个点的矩形中权值第k大的,然后我们二分答案,就成了判定性问题,判断够不够k个,这样就可以直接用扫描线做啦。然后二分的话复杂度爆炸,所以要整体二分,在整体二分时候的细节见代码,用树状数组+排序就可以解决二维数点问题。
Code
#include <bits/stdc++.h>
#define N 40005
using namespace std;
struct nod
{
int x,y,z,v,w;
nod(int a=0,int b=0,int c=0,int d=0,int e=0){x=a,y=b,z=c,v=d,w=e;}
bool operator<(const nod &a)const{return x<a.x;}
}a[N*3],q[N*2],t[N*2];
int n,m,k,x,y,z,g,he[N],to[N<<1],ne[N<<1],cnt,anc[N][20],dep[N],in[N],ou[N],I,tot,f[N],val[N],vl[N],ans[N];
bool cmp(nod a,nod b){return a.v<b.v;}
void add(int x,int y){to[++cnt]=y,ne[cnt]=he[x],he[x]=cnt;}
void dfs(int x)
{
in[x]=++I;for(int i=1;i<=15;i++) anc[x][i]=anc[anc[x][i-1]][i-1];
for(int i=he[x],y;i;i=ne[i]) if((y=to[i])!=anc[x][0]) anc[y][0]=x,dep[y]=dep[x]+1,dfs(y);
ou[x]=I;
}
int find(int x,int y){for(int i=15;~i;i--) if((1<<i)<=y) x=anc[x][i],y-=(1<<i);return x;}
void upd(int x,int a){for(int i=x;i<=n;i+=i&-i) f[i]+=a;}
int que(int x){int ans=0;for(int i=x;i;i-=i&-i) ans+=f[i];return ans;}
void solve(int b,int e,int l,int r,int L,int R)//be:询问 lr:修改 LR:权值
{
if(b>e) return;
if(L==R){for(int i=b;i<=e;i++) ans[q[i].z]=L;return;}//权值只有一种,直接记录区间内的答案
int MID=(L+R)>>1,mid=l-1,p=l;//整体二分时,只用扫左一半的修改,然后如果数量不够就在以后的右边进行递归处理,够就在以后的左边进行递归处理
for(int i=b;i<=e;i++) vl[q[i].z]=0;sort(a+l,a+r+1,cmp);
while(mid<r&&a[mid+1].v<=MID) mid++;sort(a+l,a+mid+1);
for(int i=b;i<=e;i++)
{
while(p<=mid&&a[p].x<=q[i].x) upd(a[p].y,a[p].w),upd(a[p].z+1,-a[p].w),p++;
vl[q[i].z]+=que(q[i].y);//对每个点累加覆盖它的矩阵
}
while(p>l) p--,upd(a[p].y,-a[p].w),upd(a[p].z+1,a[p].w);//还原
for(int i=(p=b);i<=e;i++) if(val[q[i].z]<=vl[q[i].z]) t[p++]=q[i];//够的
for(int i=(p=e);i>=b;i--) if(val[q[i].z]>vl[q[i].z]) t[p--]=q[i];//不够的
for(int i=b;i<=e;q[i]=t[i],i++) if(~vl[q[i].z]&&val[q[i].z]>vl[q[i].z]) val[q[i].z]-=vl[q[i].z],vl[q[i].z]=-1;//减去已经覆盖的
solve(b,p,l,mid,L,MID),solve(p+1,e,mid+1,r,MID+1,R);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=2;i<=n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
dfs(1);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
if(dep[x]>dep[y]) swap(x,y);
if(dep[x]<dep[y]&&anc[g=find(y,dep[y]-dep[x]-1)][0]==x)//x是y的祖先,xy路径上x的直系儿子子树之外->y子树之内的路径都可以
{
a[++tot]=nod(1,in[y],ou[y],z,1);
a[++tot]=nod(in[g],in[y],ou[y],z,-1);
a[++tot]=nod(ou[g]+1,in[y],ou[y],z,1);//右端点是n,所以不用添加
}
else a[++tot]=nod(in[x],in[y],ou[y],z,1),a[++tot]=nod(ou[x]+1,in[y],ou[y],z,-1);//x和y没有祖先关系,x的子树内->y的子树内的路径都可以
}
for(int i=1;i<=k;i++) scanf("%d%d%d",&x,&y,&val[i]),q[i]=nod(in[x],in[y],i,0,0),q[i+k]=nod(in[y],in[x],i,0,0);
sort(q+1,q+k*2+1),solve(1,k*2,1,tot,1,1000000000);
for(int i=1;i<=k;i++)printf("%d\n",ans[i]);
}