zoukankan      html  css  js  c++  java
  • Jumping Monkey 2021CCPC网络赛重赛1011

    题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=7136

    题意:

      给一颗n个点的树,每个点标记为1到n,每个点有自己的权值ai(保证不一样)。有只猴子可以从u点跳到v点,当且仅当u点到v点的最短路径上权值最大的点是v点。问猴子从k点开始(k∈【1.n】)最大可以跳多少个点。对于样例二,猴子在点1时,它可以跳 1 -> 3 -> 2 -> 4 共4个点。

    思路:

      第一眼看过去就想到换根DP,推了半天不会转移,然后想着从最大值开始dfs维护单调栈,发现还是搞不了,太菜了QAQ  

      考虑最大权值的点,显然该连通块的每个点都可以跳到该点,该点对该连通块内所有点的贡献为1(所有点都可以跳到该点上)

      然后考虑次大权值的点,如果某个点到次大权值点的最短路径上有最大值,显然那个点无法到达次大值点,所以我们干脆把该连通块最大值点的所有连边都砍了,分成若干了连通块,每个连通块都有自己的最大值,这样就可以去求别的连通块对答案的贡献,不断重复这个过程就可以维护出每个点的最终答案。如图:(红色数字表示该点能跳多少步,黑色数字表示节点权值)

      

      这个递归过程不好实现,所以我们考虑反过来进行这个过程。

      按权值从小到大枚举点u,首先把点u单独放入一个连通块(区分一下没遍历到的点),然后遍历它的相邻节点,如果有相邻节点在另一个连通块内,那么说明另一个连通块是因为点u砍断了点u的出边而产生的(因为权值从小到大枚举,当前的权值比枚举过的权值大),把他们重新合成一个连通块,其中点u作为父节点,另一个连通块的根作为子节点,这就有点像并查集存在一个上下级关系。枚举完后,并查集就形成了一颗新树,每个节点的深度即为答案(根节点深度为1)。

      模拟过程:假设一开始的图是: 3 - 1 - 4 - 2

      从小到大枚举权值:

      第一个是1,相邻的点为3,4,都没有遍历过,所以1单独成一个连通块。

      

       第二个是2,相邻的点为4,都没有遍历过,所以2单独成一个连通块。

      

       第三个是3,相邻的点为1,说明1所在连通块是因为3而拆出来的,现在合回去。

      

       第四个是4,相邻的点为1,2,说明1,2的连通块是因为4拆出来的,连通0块1的根是3,连通块2的根是2,合起来。

      

       这颗就是新的树了,根据并查集的关系建出来的树。

      如果看着这颗树,按一开始的思路进行拆连通块统计贡献,就会发现,每在一个连通块上删一个最大值,就相当于在这颗树上删去那个点,然后这个点包含的子树贡献全部+1,和一开始的想法一样,删着删着就会发现节点深度就是我们维护的贡献,妙啊。

      反思:这道题删一个点形成多个连通块,而并查集维护的是连通块,所以应该往并查集这个方向想?有没有大佬指点下思路应该怎么往这个方向靠近啊....

    代码:

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn = 1e5 + 7;
    vector<int> E[maxn],tu[maxn];
    int n,fa[maxn],dep[maxn];
    struct node {
        int x,val;
    }a[maxn];
    bool cmp(node a,node b) {
        return a.val < b.val;
    }
    int fid(int x) {
        return x == fa[x] ? x : fa[x] = fid(fa[x]);
    }
    void dfs(int u,int fa) {
        dep[u] = dep[fa] + 1;
        for (auto v:tu[u]) {
            if(v == fa) continue;
            dfs(v,u);
        }
    }
    void solve() {
        int x,y;
        scanf("%d",&n);
        for (int i=1; i<=n; ++i) {
            E[i].clear();
            tu[i].clear();
            fa[i] = dep[i] = 0;
        }
        for (int i=1; i<=n-1; ++i) {
            scanf("%d%d",&x,&y);
            E[x].push_back(y);
            E[y].push_back(x);
        }
        for (int i=1; i<=n; ++i) {
            scanf("%d",&a[i].val);
            a[i].x = i;
        }
        sort(a+1,a+1+n,cmp);
        for (int i=1; i<=n; ++i) {
            fa[a[i].x] = a[i].x;
            for (auto v:E[a[i].x]) {
                if(fa[v]) {
                    int V = fid(v);
                    fa[V] = a[i].x; 
                    tu[V].push_back(a[i].x);
                    tu[a[i].x].push_back(V);
                }
            }
        }
        dfs(a[n].x,0);
        for (int i=1; i<=n; ++i) printf("%d
    ",dep[i]);
    }
    int main() {
        int t;
        scanf("%d",&t);
        while(t--) {
            solve();
        }
        return 0;
    }
  • 相关阅读:
    视差滚动(Parallax Scrolling)插件补充
    10个最佳的触控手式的JavaScript框架(转)
    50个必备的实用jQuery代码段(转)
    PhoneGap开发不可或缺的五件装备
    优化移动网站的9大窍门(转)
    WSADATA
    htons
    INADDR_ANY
    SOCKADDR_IN
    mysql_query()与mysql_real_query()
  • 原文地址:https://www.cnblogs.com/Remilia-Scarlet/p/15391568.html
Copyright © 2011-2022 走看看