洛谷应该是疯了,又给我推荐黑题,又花了一上午
闲话少说,先给链接
大致题意描述(语文不好请见谅):
给一棵树,再给P条路径,每条路径有权值。
有Q次询问,每次给一条路径和k,让你输出能完全覆盖这条路径的 第k大路径(的权值)
(a --> b) & (b --> a) are the same
我知道您肯定没看懂,所以您还是去打开链接看吧
先考虑如何得到完全覆盖的路径:
设水果路径起点为x,终点为y,且dep[x]<dep[y]
设盘子路径起点为a,终点为b
设一点dfs序为dfn,子树dfn最大的点的dfn为low(即dfn~low为这一棵子树的所有节点)
若lca(x,y)==x:
设w为 x --> y 路径上的第一个点(不包括x)
则有a在y子树内,b不在w子树内
即:dfn[y]<=dfn[a]<=low[y],且(1<=dfn[b]<dfn[w] || low[w]<dfn[b]<=n)
else:
a、b两点都分别在x、y子树内
即:dfn[x]<=dfn[a]<=low[x],且dfn[y]<=dfn[b]<=low[y]
可以看出,我们成功把盘子变成了1或2个矩形,水果变成了点
那么题目转换为:求覆盖一个点的权值第k大的矩形(的权值)
考虑整体二分(不会的左转百度,因为我也不会)
在二分过程中,先用扫描线算法把矩形优化为一维线
然后将区间加改为差分,用树状数组维护前缀和(即单点值)
就可以求出每个点被覆盖了多少次辣
代码:
#include<bits/stdc++.h>
#define sz 40040
using namespace std;
int n,m,Q,i,x,y,z,q[sz],tmp[sz],now[sz],ans[sz];
struct E{
int x1,x2,l1,r1,l2,r2,z;
E(){}
E(int _x1,int _x2,int _l1,int _r1,int _l2,int _r2,int _z){x1=_x1,x2=_x2,l1=_l1,r1=_r1,l2=_l2,r2=_r2,z=_z;}
}e[sz];//盘子
inline bool cmp(const E&a,const E&b){return a.z<b.z;}
struct P{int x,y,k,p;P(){}P(int _x,int _y,int _k,int _p){x=_x,y=_y,k=_k,p=_p;}}a[sz];//水果
struct B{int x,l,r,t;B(){}B(int _x,int _l,int _r,int _t){x=_x,l=_l,r=_r,t=_t;}}b[sz<<1];
inline bool cmpb(const B&a,const B&b){return a.x<b.x;}
struct C{int x,y,p;C(){}C(int _x,int _y,int _p){x=_x,y=_y,p=_p;}}c[sz<<1];
inline bool cmpc(const C&a,const C&b){return a.x<b.x;}
struct hh{int t,nxt;}edge[sz<<1];
int head[sz],ecnt;
void make_edge(int f,int t)
{
edge[++ecnt]=(hh){t,head[f]};
head[f]=ecnt;
edge[++ecnt]=(hh){f,head[t]};
head[t]=ecnt;
}
int T,pos[sz],bit[sz];
void add(int x,int y){while (x<=n){if (pos[x]<T) bit[x]=y;else bit[x]+=y;pos[x]=T;x+=(x&-x);}}
int query(int x){int ret=0;while (x){if (pos[x]==T) ret+=bit[x];x-=(x&-x);}return ret;}
int top[sz],son[sz],fa[sz],dep[sz],dfn[sz],low[sz],size[sz],cnt;
#define go(x) for (int i=head[x];i;i=edge[i].nxt)
#define o edge[i].t
void dfs1(int x)
{
size[x]=1;dep[x]=dep[fa[x]]+1;
go(x) if (o!=fa[x])
{
fa[o]=x;
dfs1(o);
size[x]+=size[o];
if (size[o]>size[son[x]]) son[x]=o;
}
}
void dfs2(int x,int tp)
{
dfn[x]=++cnt;top[x]=tp;
if (!son[x]) return (void)(low[x]=cnt);
dfs2(son[x],tp);
go(x) if (!dfn[o]) dfs2(o,o);
low[x]=cnt;
}
int lca_son(int x,int y)
{
int t;
if (dep[x]<dep[y]) swap(x,y);
while(top[x]!=top[y]) x=fa[t=top[x]];
return x==y?t:son[y];
}
inline int read()
{
register int ret=0;
register char ch=getchar();
while (ch>'9'||ch<'0') ch=getchar();
while (ch<='9'&&ch>='0') ret=ret*10+ch-48,ch=getchar();
return ret;
}
void init()
{
n=read();m=read();Q=read();
int i,j,x,y,z;
for (i=1;i<n;i++) x=read(),y=read(),make_edge(x,y);
dfs1(1);dfs2(1,1);
for (i=1;i<=m;i++)
{
x=read();y=read();z=read();
if (dep[x]<dep[y]) swap(x,y);
if (dfn[y]<=dfn[x]&&low[x]<=low[y]) j=lca_son(x,y),e[i]=E(dfn[x],low[x],1,dfn[j]-1,low[j]+1,n,z);
else e[i]=E(dfn[x],low[x],dfn[y],low[y],1,0,z);
}
sort(e+1,e+m+1,cmp);
for (i=1;i<=Q;i++)
{
x=read(),y=read(),z=read();
a[i]=P(dfn[x],dfn[y],z,i);q[i]=i;
}
}
void solve(int l,int r,int ql,int qr)
{
if (ql>qr) return;
int i,j,L=ql,R=qr;
if (l==r) {for (i=ql;i<=qr;i++) ans[a[q[i]].p]=e[l].z,cout<<(a[q[i]].k>1?"jkhh":"");return;}
int mid=(l+r)>>1,cb=0,cc=0;
for (i=l;i<=mid;i++)
{
if (e[i].l1<=e[i].r1)
{
b[++cb]=B(e[i].x1,e[i].l1,e[i].r1,1);
b[++cb]=B(e[i].x2+1,e[i].l1,e[i].r1,-1);
}
if (e[i].l2<=e[i].r2)
{
b[++cb]=B(e[i].x1,e[i].l2,e[i].r2,1);
b[++cb]=B(e[i].x2+1,e[i].l2,e[i].r2,-1);
}
}
for (i=ql;i<=qr;i++)//一个拆成俩(别问我为什么,我也不知道)
{
c[++cc]=C(a[q[i]].x,a[q[i]].y,i);
c[++cc]=C(a[q[i]].y,a[q[i]].x,i);
now[i]=0;
}
T++,sort(b+1,b+cb+1,cmpb),sort(c+1,c+cc+1,cmpc);
for (i=1,j=1;i<=cc;i++)
{
while (j<=cb&&b[j].x<=c[i].x) add(b[j].l,b[j].t),add(b[j].r+1,-b[j].t),j++;//差分加扫描线
now[c[i].p]+=query(c[i].y);
}
for (i=ql;i<=qr;i++) if (now[i]>=a[q[i]].k) tmp[L++]=q[i];else {tmp[R--]=q[i];a[q[i]].k-=now[i];}
for (i=ql;i<=qr;i++) q[i]=tmp[i];
solve(l,mid,ql,R);solve(mid+1,r,R+1,qr);
}
int main()
{
init();
solve(1,m,1,Q);
for (int i=1;i<=Q;i++) printf("%d\n",ans[i]);
}

这篇博客介绍了HNOI2015中的一道题目,涉及一棵树上的路径权值和覆盖问题。博主探讨了如何将问题转化为求解覆盖一个点的权值第k大的矩形,并通过二分法和树状数组来优化解决方案。主要内容包括如何找到完全覆盖路径,以及利用dfs序和低点进行路径分析。
431

被折叠的 条评论
为什么被折叠?



