You are given a tree with N N N nodes. The tree nodes are numbered from 1 to N N N. Each node has an integer weight.
We will ask you to perform the following operation:
u v k u\ v\ k u v k : ask for the k t h kth kth minimum weight on the path from node u u u to node v v v
In the first line there are two integers N N N and M M M. ( N , M < = 1 e 5 ) (N, M <= 1e5) (N,M<=1e5)
In the second line there are N N N integers. The ith integer denotes the weight of the ith node.
In the next N − 1 N-1 N−1 lines, each line contains two integers u u u v v v, which describes an edge ( u u u, v v v).
In the next M M M lines, each line contains three integers u v k u\ v\ k u v k, which means an operation asking for the k t h kth kth minimum weight on the path from node u to node v v v.
For each operation, print its result.
8 5
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
2 5 2
2 5 3
2 5 4
7 8 2
2
8
9
105
7
大致题意就是:给出一棵树,每个结点上都有一个权值,每次询问时给定 u v k u\ v\ k u v k,找出 u u u到 v v v最短路径中第 k k k小的点的权值。
维护区间第 k k k大的问题可以用主席树解决,在遍历树的同时建立一颗主席树,也要处理树中每个节点的深度。
处理完之后我们可以利用LCA来算出 u u u到 v v v的节点个数,然后扔到主席树里求第k小即可。
对于求LCA的部分,笔者采用了倍增求法,离线的Tarjan还待确认(咕咕咕)。
PS:题面中有一个含混不清的地方——点权值的大小范围,经测试发现int范围不会翻车,但可能超过 1 e 5 1e5 1e5。
#include
using namespace std;
const int maxn=1e5;
struct Edge
{
int to, next;
};
Edge edge[(maxn<<1)+10];//链式前向星存图
int Head[maxn+10];
int sumEdge=0;
int f[maxn+10][31];//f[i][j]表示i的第2^j个祖先
int depth[maxn+10];
int n, q, m, cnt=0;
const int Mul=30;
int a[maxn+10], b[maxn+10], T[maxn+10];
int sum[maxn*Mul+10], L[maxn*Mul+10], R[maxn*Mul+10];
inline void add_edge(int u, int v)//建边
{
edge[++sumEdge].to=v;
edge[sumEdge].next=Head[u];
Head[u]=sumEdge;
}
inline void Edge_Print(int size)//输出整个图,调试用
{
for(int i=1; i<=size; i++)
{
printf("%d\n", i);
for(int j=Head[i]; j; j=edge[j].next)
{
printf("%d ", edge[j].to);
}
printf("\n");
}
}
void init()//(不知道有没有用的)初始化
{
memset(Head, -1, sizeof(Head));
memset(edge, 0, sizeof(edge));
memset(Head, 0, sizeof(Head));
sumEdge=0;
}
int getLCA(int u, int v)//求LCA
{
if (depth[u]<depth[v])
swap(u, v);
for(int i=20; i>=0; i--)
if (depth[f[u][i]]>=depth[v]) u=f[u][i];
if (u==v)
return u;
for(int i=20; i>=0; i--)
if (f[u][i]!=f[v][i])
{
u=f[u][i];
v=f[v][i];
}
return f[u][0];
}
inline int President_Tree_build(int l, int r)//主席树建树
{
int rt=++cnt;
sum[rt]=0;
if (l<r)//这里不执行的时候就是叶子节点
{
int mid=(l+r)>>1;
L[rt]=President_Tree_build(l, mid);
R[rt]=President_Tree_build(mid+1, r);
}
return rt;
}
inline int President_Tree_update(int pre, int l, int r, int x)//开新节点
{
int rt=++cnt;//开辟新节点
L[rt]=L[pre];
R[rt]=R[pre];//
sum[rt]=sum[pre]+1;//更新个数
if (l<r)
{
int mid=(l+r)>>1;
if (x<=mid) L[rt]=President_Tree_update(L[pre], l, mid, x);
else R[rt]=President_Tree_update(R[pre], mid+1, r, x);
}
return rt;
}
inline int President_Tree_query(int ql, int qr, int k, int l, int r)//查询
{
int lca=getLCA(ql, qr);
int lf=f[lca][0];
ql=T[ql], qr=T[qr], lca=T[lca], lf=T[lf];
while(l<r)
{
int mid=(l+r)>>1;
if (k<=(sum[L[ql]]+sum[L[qr]]-sum[L[lca]]-sum[L[lf]]))
{
ql=L[ql];
qr=L[qr];
lca=L[lca];
lf=L[lf];
r=mid;
}
else
{
k-=(sum[L[ql]]+sum[L[qr]]-sum[L[lca]]-sum[L[lf]]);
ql=R[ql];
qr=R[qr];
lca=R[lca];
lf=R[lf];
l=mid+1;
}
}
return l;
}
inline void depth_Print()//调试用,输出节点深度
{
for(int i=1; i<=sumEdge/2; i++)
{
printf("%d ", depth[i]);
}
printf("\n");
}
void LCA_dfs(int u, int pre)//遍历深度
{
int tt=lower_bound(b+1, b+1+m, a[u])-b;
T[u]=President_Tree_update(T[pre], 1, m, tt);//建立主席树
depth[u]=depth[pre]+1;
f[u][0]=pre;
for(int i=1; (1<<i)<=depth[u]; i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int k=Head[u]; k; k=edge[k].next)
{
if (edge[k].to!=pre)
LCA_dfs(edge[k].to, u);
}
}
void solve()
{
init();
scanf("%d%d", &n, &q);
for(int i=1; i<=n; i++)
{
scanf("%d", &a[i]);
b[i]=a[i];
}
sort(b+1, b+1+n);
m=unique(b+1, b+1+n)-(b+1);
T[0]=President_Tree_build(1, m);
for(int i=1; i<=n-1; i++)
{
int u, v;
scanf("%d%d", &u, &v);
add_edge(u, v);
add_edge(v, u);//双向边
}
LCA_dfs(1, 0);
// depth_Print();
while(q--)
{
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
int lca=getLCA(l, r);
// printf("%d\n",lca);
int index=President_Tree_query(l, r, k, 1, m);
// printf("index=%d\n",index);
printf("%d\n", b[index]);
}
}
signed main()
{
// ios_base::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
long long test_index_for_debug=1;
char acm_local_for_debug;
while(cin>>acm_local_for_debug)
{
cin.putback(acm_local_for_debug);
if (test_index_for_debug>100)
{
throw runtime_error("Check the stdin!!!");
}
auto start_clock_for_debug=clock();
solve();
auto end_clock_for_debug=clock();
cout<<"\nTest "<<test_index_for_debug<<" successful"<<endl;
cerr<<"Test "<<test_index_for_debug++<<" Run Time: "
<<double(end_clock_for_debug-start_clock_for_debug)/CLOCKS_PER_SEC<<"s"<<endl;
cout<<"--------------------------------------------------"<<endl;
}
#else
solve();
#endif
return 0;
}
交了大概20次,从昨天中午改到今天早上,期间经历过但不限于:
1.建树的姿势错误
2.LCA板子翻车
3.各种七七八八的RE,比如图是双向边没开两倍空间、主席树没开够40倍之类的…
综上,特此开博客记录。
DrGilbert 2020.8.18