zoukankan      html  css  js  c++  java
  • [HNOI/AHOI2018]排列 贪心

    题面
    题解:
    把题面的限制换成中文:
    如果排在第k位的下标 = 排在第j位的值 ,那么k < j
    换一个描述方式:
    一个值为x的数要排在第x个数后面。
    再换一个描述方式:
    (fa[i] = a_i)

    仿佛有什么不对劲?
    嗯这其实就是一棵树。
    并且我们可以发现,这棵树一定以一个虚拟节点0为根,并且有合法排列,也就是树没有环,当且仅当从0开始遍历,可以遍历到所有节点。
    且排列合法当且仅当我们在访问一个节点之前,先访问它的父亲,也就相当于在树上走。

    因为如果不以0为根,那么由于题面中所说权值大小在([1, n])的区间内,我们根本无法找到进入这样一些关系的"入口",因为不管我们尝试从那个点开始走,都必须要先去这个点的父亲,而要去这个点的父亲,又要先去这个点的父亲的父亲……因为在这棵树中,我们唯一可以直接到达的点是虚拟节点0,所以如果这些关系组成的边无法通向0的话,我们只能在无限个这样的限制中绕圈,永远无法找到入口,即没有合法的解。

    因此我们真正的题面其实是:
    给定一个n + 1个节点的树,依次取点,满足取儿子之前,必须要取它的父亲。每个点有权值,第i个取的点的权值会被乘上i,求一个合法方案,使得取完所有点后权值之和最大。

    根据贪心的原则,一个点的权值越小,就越要优先取。

    因此我们考虑整棵树中权值最小的那个节点。

    1,如果这个节点没有父亲(或父亲已经被取走),那我们肯定先取它。
    2,如果这个点有父亲,那么一旦我们取了它的父亲,我们肯定会马上就取它(此时满足情况1)

    综上,我们得到一个结论,如果我们每次考虑权值最小的那个节点,这个节点要么没有父亲(f[i] = 0),要么一定会紧挨着它的父亲取。
    所以这个点和它的父亲在最优排列中一定是相邻的,因此我们可以考虑合并这2个节点。
    那么实际上,树中的每个节点就代表了一段排列,现在来考虑一段排列怎么评估一段排列的权值

    假设有一个长度为(m_1)的序列(a),和一段长度为(m_2)的序列(b),我们表示出序列(ab)和序列(ba)的权值

    [W_{ab} = sum_{j = 1}^{m_1}(i + j)W_{a_j} + sum_{j = 1}^{m_2}(i + j + m_1)W_{b_j} ]

    [W_{ab} = sum_{j = 1}^{m_2}(i + j)W_{b_j} + sum_{j = 1}^{m_1}(i + j + m_2)W_{a_j} ]

    如果排列(ab)优于排列(ba),那么(W_{ab} > W_{ba}),即先选(a)更优
    那么有:

    [W_{ab} - W_{ba} = m_1W_b - m_2W_a > 0 ]

    [frac{W_b}{m_2} > frac{W_a}{m_1} ]

    因此我们只需要以平均权值作为新的权值来考虑即可。

    根据上诉式子,可以得到,如果我们把序列(b)放在序列(a)后面,可以得到一个独立的(m_1 cdot W_b)的贡献,因此在不断合并节点的过程中统计贡献即可

    (Delta)此题有点卡精度,请用long double……

    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define AC 501000
    #define LL long long
    #define ld long double 
    
    int n, rnt;
    LL ans, sum[AC];
    int v[AC], fa[AC], cnt[AC];//cnt记录每个节点的版本号
    int Next[AC], last[AC], belong[AC], Size[AC];//用并查集+双向链表来维护已经合并的序列
    
    struct node{int id, x; ld w;};//当前点版本号+编号+值
    struct cmp{bool operator() (node a, node b){return a.w > b.w;}};
    
    struct road{
        int Head[AC], Next[AC], date[AC], tot;
        inline void add(int f, int w){
            date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot;
        }//用父亲向儿子连边
    }E;
    
    
    node _s[AC]; int _top;//check
    
    
    struct STL_Delete_queue{
        priority_queue<node, vector<node>, cmp> q1;
        void del(int x){++ cnt[x];}//删除编号为x的并查集
        void push(node x){q1.push(x);}
        
        int top()
        {
            while(!q1.empty() && q1.top().id != cnt[q1.top().x]) q1.pop();	
            int x = q1.top().x;
            q1.pop();
            return x;
        }
        
        void check()
        {
            _top = 0;
            while(!q1.empty()) _s[++ _top] = q1.top(), q1.pop();
            for(R i = 1; i <= n; i ++) 
                printf("%d %.3Lf
    ", _s[i].x, _s[i].w);
            for(R i = 1; i <= _top; i ++) q1.push(_s[i]);
        }
    }q;
    
    
    inline int read()
    {
        int x = 0;char c = getchar();
        while(c > '9' || c < '0') c = getchar();
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x;
    }
    
    
    
    int find(int x)
    {
        if(belong[x] == x) return x;
        else return belong[x] = find(belong[x]);
    }
    
    void dfs1(int x)//先判断是否合法
    {
        ++ rnt;//和0相连的点必定形成一个合法的树,其他不合法的环都是独立出来的。
        for(R i = E.Head[x]; i != -1; i = E.Next[i]) 
            dfs1(E.date[i]);//所以只需要记录从0开始遍历,可以遍历到多少点即可
    }	
    
    void pre()
    {
        n = read();
        for(R i = 0; i <= n; i ++) E.Head[i] = Next[i] = last[i] = -1;//因为有0号节点,因此先全都赋值为-1
        for(R i = 1; i <= n; i ++) fa[i] = read(), E.add(fa[i], i), belong[i] = i;
        for(R i = 1; i <= n; i ++) 
        {
            v[i] = sum[i] = read(), ans += v[i];
            q.push((node){0, i, (ld)v[i]}), Size[i] = 1; //维护集合的信息
        }
    }
    
    void work()
    {
        for(R i = 1; i <= n; i ++)//因为一共有n + 1个节点,所以会合并n次
        {
            int x = q.top(), fx = find(fa[x]);
            belong[x] = fx, q.del(fx);
            ans += Size[fx] * sum[x], Size[fx] += Size[x], sum[fx] += sum[x];
            if(fx) q.push((node){cnt[fx], fx, (ld)sum[fx] / (ld)Size[fx]});
        }	
        printf("%lld
    ", ans);
    }
    
    int main()
    {
    //	freopen("in.in", "r", stdin);
        pre();
        dfs1(0);
        if(rnt != n + 1) printf("-1
    ");
        else work();
    //	fclose(stdin);
        return 0;
    }
    
  • 相关阅读:
    Py修行路 python基础 (二十五)线程与进程
    Py修行路 python基础 (二十一)logging日志模块 json序列化 正则表达式(re)
    Py修行路 python基础 (二十四)socket编程
    Py修行路 python基础 (二十三)模块与包
    Py修行路 python基础 (二十二)异常处理
    Py修行路 python基础 (二十)模块 time模块,random模块,hashlib模块,OS及sys模块
    Py修行路 python基础 (十九)面向对象进阶(下)
    Oracle数据库的下载和安装
    单体测试详解
    单体测试书的检查要点
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10277950.html
Copyright © 2011-2022 走看看