SPOJ - COT Count on a tree (LCA+主席树)

  • 原题题面
    • 输入格式
    • 输出格式
    • 输入样例
    • 输出样例
  • 题面分析
  • AC代码(2260ms)
  • 后记

原题题面

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 N1 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

AC代码(2260ms)

#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

你可能感兴趣的:(数据结构,主席树,ACM,LCA)