其实我真的不想写这道题,然而迫于有一个技巧,不得不写一写(主要是为了把技巧放进博客)
保证一个月内不写代码超过100行的。
[HNOI2015]接水果
Description
风见幽香非常喜欢玩一个叫做 osu!的游戏,其中她最喜欢玩的模式就是接水果。
由于她已经DT FC 了The big black, 她觉得这个游戏太简单了,于是发明了一个更
加难的版本。首先有一个地图,是一棵由
n
n
个顶点、 条边组成的树(例如图 1
给出的树包含 8 个顶点、7 条边)。这颗树上有
P
P
个盘子,每个盘子实际上是一条
路径(例如图 1 中顶点 6 到顶点 8 的路径),并且每个盘子还有一个权值。第 个
盘子就是顶点
ai
a
i
到顶点
bi
b
i
的路径(由于是树,所以从
ai
a
i
到
bi
b
i
的路径是唯一的),权值为
ci
c
i
。接下来依次会有
Q
Q
个水果掉下来,每个水果本质上也是一条路径,第 个水果是从顶点
ui
u
i
到顶点
vi
v
i
的路径。幽香每次需要选择一个盘子去接当前的水果:一个盘子能接住一个水果,当且仅当盘子的路径是水果的路径的子路径(例如图1中从 3到7 的路径是从1到8的路径的子路径)。这里规定:从
a
a
到b的路径与从到
a
a
的路径是同一条路径。当然为了提高难度,对于第 个水果,你需要选择能接住它的所有盘子中,权值第
ki
k
i
小的那个盘子,每个盘子可重复使用(没有使用次数的上限:一个盘子接完一个水果后,后面还可继续接其他水果,只要它是水果路径的子路径)。幽香认为这个游戏很难,你能轻松解决给她看吗?
Input
第一行三个数 n n 和 和 Q Q ,表示树的大小和盘子的个数和水果的个数。
接下来 行,每行两个数
a
a
、,表示树上的
a
a
和 之间有一条边。树中顶点按1到
n
n
标号。 接下来 行,每行三个数
a
a
、、
c
c
,表示路径为 到
b
b
、权值为 的盘子,其中
0≤c≤109
0
≤
c
≤
10
9
,
a
a
不等于。
接下来
Q
Q
行,每行三个数 、
v
v
、,表示路径为
u
u
到的水果,其中
u
u
不等于,你需要选择第
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
N
,
P
,
Q
<=
40000
。
解:
这道题首先需要解决的问题是如何判断水果是否出现在盘子上。而且,我们对于一个盘子,要很快地求出有多少个水果经过它。
这里搬一个结论,还是蒟蒻太菜了,根本不会。
搬运
首先得到每个节点的序和以这个节点为根的子树中
dfs
d
f
s
序最大值,分别记作
dfn[u]
d
f
n
[
u
]
和
last[u]
l
a
s
t
[
u
]
显然,A路径覆盖B路径当且仅当B路径的两个端点都在A路径上
对于每一个盘子 (u,v) ( u , v ) (假设 dfn[u]<dfn[v] d f n [ u ] < d f n [ v ] ),有以下两种情况
1. lca(u,v)!=u l c a ( u , v ) ! = u 时,如图
(虚线表示有一条路径,实线表示一条边,下同)
显然这个盘子能接到的水果 (a,b) ( a , b ) (假设 dfn[a]<dfn[b] d f n [ a ] < d f n [ b ] ,下同)满足 dfn[u]<=dfn[a]<=last[u] d f n [ u ] <= d f n [ a ] <= l a s t [ u ] && dfn[v]<=dfn[b]<=last[v] d f n [ v ] <= d f n [ b ] <= l a s t [ v ]
2.当 lca(u,v)==u l c a ( u , v ) == u 时,如图
设 w w 为在 (u,v) ( u , v ) 这条路径上的儿子节点
则这个盘子能接到的水果 (a,b) ( a , b ) 满足 ((1<=dfn[a]<=dfn[w]−1)||(last[w]+1<=dfn[a]<=n)) ( ( 1 <= d f n [ a ] <= d f n [ w ] − 1 ) | | ( l a s t [ w ] + 1 <= d f n [ a ] <= n ) ) && (dfn[v]<=dfn[b]<=last[v]) ( d f n [ v ] <= d f n [ b ] <= l a s t [ v ] )
我们可以将水果
(a,b)
(
a
,
b
)
看成平面上的一个点
(dfn[a],dfn[b])
(
d
f
n
[
a
]
,
d
f
n
[
b
]
)
,将盘子看成一个或两个矩形
那么问题就转化为对于平面上的一个点,求覆盖它的第
k
k
小的矩形
这是一个整体二分的经典题,用扫描线+树状数组搞定
我们先将矩形按权值从小到大排序
然后对于一个点,如果[l,mid]中能覆盖这个点的矩形数不小于,则说明答案在[l,mid]中
否则在[mid+1,r],同时
k
k
<script type="math/tex" id="MathJax-Element-295">k</script>减去覆盖的矩形数
大佬说的很清楚啦~
发现整体二分似乎并不能先把矩阵拆成扫描线,要在二分中间拆成扫描线。不知道是不是姿势不对?似乎两个边界会被分到两边,对答案有问题?
我就只好把矩形二分,然后要算的时候就直接拆成扫描线。
唉,怕不是写假了,别人都只写了100行多一点。不知道是树剖长了还是整体二分长了。怕不是都长了。
反正都180行了,根本不想卡常,sort算了。-_-
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(x) ((x)&(-x))
using namespace std;
int key;
struct lxy{
int next,to;
}eg[80005];
struct lxy2{
int xl,xr,yl,yr,k;
bool operator < (const lxy2 &QAQ)const{
if(key==0) return k<QAQ.k;
}
}d[200005];
struct lxy3{
int l,r,k,tim,ans;
bool operator < (const lxy3 &QAQ)const{
if(key==0) return tim<QAQ.tim;
if(key==1) return l<QAQ.l;
}
}f[40005],tax[40005];
struct lxy4{
int x,yl,yr,type;
bool operator < (const lxy4 &QAQ)const{
if(key==1) return x<QAQ.x;
}
}s[200005];
int n,p,q,x,y,k,cnt,tim,kl;
int head[40005],tp[40005],fa[40005],dep[40005],size[40005],wson[40005],dfn[40005],last[40005];
bool vis[40005];
int b[40005];
void add(int op,int ed)
{
eg[++cnt].next=head[op];
eg[cnt].to=ed;
head[op]=cnt;
}
void dfs1(int u,int dp)
{
vis[u]=1;dep[u]=dp;size[u]=1;dfn[u]=++tim;
int t=0;
for(int i=head[u];i!=-1;i=eg[i].next)
if(vis[eg[i].to]==0)
{
fa[eg[i].to]=u;
dfs1(eg[i].to,dp+1);
size[u]+=size[eg[i].to];
if(size[eg[i].to]>t){
t=size[eg[i].to];
wson[u]=eg[i].to;
}
}
vis[u]=0;
}
int dfs2(int u,int las)
{
tp[u]=las;vis[u]=1;last[u]=dfn[u];
if(wson[u]!=0) last[u]=max(last[u],dfs2(wson[u],las));
for(int i=head[u];i!=-1;i=eg[i].next)
if(vis[eg[i].to]==0&&eg[i].to!=wson[u])
last[u]=max(last[u],dfs2(eg[i].to,eg[i].to));
vis[u]=0;
return last[u];
}
bool lca(int w,int v)
{
int fro=v;
while(tp[w]!=tp[v]&&v!=0){
fro=tp[v],v=fa[tp[v]];
}
if(v==0||dep[v]<dep[w]) return true;
if(v==w) kl=fro;
else kl=wson[w];
return false;
}
void modify(int u,int r){
while(u<=n){
b[u]+=r;
u+=lowbit(u);
}
}
int query(int u){
int ret=0;
while(u>0){
ret+=b[u];
u-=lowbit(u);
}
return ret;
}
void CDQ(int ml,int mr,int ql,int qr)
{
if(ml==mr){
for(int i=ql;i<=qr;i++)
f[i].ans=d[ml].k;
return;
}
int mid=(ml+mr)>>1,cnt_=0;
for(int i=ml;i<=mid;i++){
s[++cnt_].x=d[i].xl;s[cnt_].yl=d[i].yl;s[cnt_].yr=d[i].yr;s[cnt_].type=1;
s[++cnt_].x=d[i].xr;s[cnt_].yl=d[i].yl;s[cnt_].yr=d[i].yr;s[cnt_].type=-1;
}
int t1=1,t2=ql,l=ql-1,r=qr+1;
key=1,sort(s+1,s+cnt_+1),sort(f+ql,f+qr+1);
while(t2<=qr){
if(t1<=cnt_&&s[t1].x<=f[t2].l){
modify(s[t1].yl,s[t1].type);
modify(s[t1].yr+1,-s[t1].type);
t1++;
}
else{
int z=query(f[t2].r);
if(z<f[t2].k) f[t2].k-=z,tax[--r]=f[t2];
else tax[++l]=f[t2];
t2++;
}
}
for(int i=1;i<t1;i++){
modify(s[i].yl,-s[i].type);
modify(s[i].yr+1,s[i].type);
}
for(int i=ql;i<=qr;i++)
f[i]=tax[i];
CDQ(ml,mid,ql,l);
CDQ(mid+1,mr,r,qr);
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d%d",&n,&p,&q);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,1);dfs2(1,1);
cnt=0;
for(int i=1;i<=p;i++){
scanf("%d%d%d",&x,&y,&k);
if(dfn[x]>dfn[y]) swap(x,y);
if(lca(x,y)){
d[++cnt].xl=dfn[x];d[cnt].xr=last[x]+1;d[cnt].yl=dfn[y];d[cnt].yr=last[y];d[cnt].k=k;
}
else{
d[++cnt].xl=1;d[cnt].xr=dfn[kl];d[cnt].yl=dfn[y];d[cnt].yr=last[y];d[cnt].k=k;
d[++cnt].xl=dfn[y];d[cnt].xr=last[y]+1;d[cnt].yl=last[kl]+1;d[cnt].yr=n;d[cnt].k=k;
}
}
for(int i=1;i<=q;i++){
scanf("%d%d%d",&x,&y,&f[i].k);
f[i].tim=i;
f[i].l=min(dfn[x],dfn[y]);
f[i].r=max(dfn[x],dfn[y]);
}
key=0,sort(d+1,d+1+cnt);
CDQ(1,cnt,1,q);
key=0,sort(f+1,f+q+1);
for(int i=1;i<=q;i++)
printf("%d\n",f[i].ans);
}