Luogu4437 HNOI/AHOI2018 排列 贪心

传送门


一种很经典的贪心套路~

首先考虑合法排列的限制条件,也就是在排列\(p\)中,对于\(\forall i \in [1,N]\) , \(a_i\)要出现在\(i\)的前面。我们连边\((i,a_i)\),表示\(i\)要在\(a_i\)之后出现。那么如果说存在合法的排列,那么最后连成的图一定是一棵树,否则存在环,限制条件冲突。

我们现在知道原图是一棵树了,对于\(\forall i \in [1,N]\) , 令\(i\)的父亲为\(a_i\)。那么对于所有的点,必须要在它的所有祖先选完了之后才能选到它自己。

然后如何去求权值最大值呢?

有一种比较naive的贪心:每一次都选择当前可以选择的点中最小的。

这种贪心策略显然是错的,反例也很好举……

但是这似乎能给我们一些启发:对于当前所有点权中最小的数,它一定会在它的父亲被选完之后立即选。

不妨设当前所有点权中最小点权对应的点权为\(x\),它的父亲权为\(y\),有另一个点权为\(z\),并且满足\(y\)对应的点和\(z\)对应的点现在都可以选

那么现在有两种决策:

1、先选\(y,x\),后选\(z\),权值和为\(y+2x+3z\)

2、先选\(z\),后选\(y,x\),权值和为\(z+2y+3x\)

我们需要取其中更优的,所以我们只需要比较它们的大小。考虑两者同时减去\(x-z\),除以\(2\)

那么第一种方案的权值为\(\frac{x+y}{2}+2z\),第二种方案的权值为\(z + 2\frac{x+y}{2}\)

可以发现之前的两个点\(x,y\)在这个时候可以等价为一个点权为\(\frac{x+y}{2}\)的点

那么可以得到一个贪心:每一次选择一个点权最小的点,把它和它父亲合并,对于一个点的权值为:\(\frac{\text{它所包含的点的点权和}}{\text{它所包含的点的个数}}\)

并查集维护合并过程、在堆中每一次取最小点即可。

最后计算答案时再把合并的点拆开。

#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c)){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    while(isdigit(c)){
        a = (a << 3) + (a << 1) + (c ^ '0');
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 5e5 + 10;
struct Edge{
    int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , pre[MAXN << 1] , fa[MAXN << 1] , ch[MAXN << 1][2] , size[MAXN << 1] , cntEd , cntNode , N , t;
long long pri[MAXN << 1] , ans;
bool vis[MAXN] , mark[MAXN << 1];
struct cmp{
    bool operator ()(int a , int b){
        return pri[a] * size[b] > pri[b] * size[a];
    }
};
priority_queue < int , vector < int > , cmp > q;

int find(int x){
    return fa[x] == x ? x : (fa[x] = find(fa[x]));
}

inline void addEd(int a , int b){
    Ed[++cntEd].end = b;
    Ed[cntEd].upEd = head[a];
    head[a] = cntEd;
}

bool dfs(int x , int p){
    pre[x] = p;
    vis[x] = 1;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(Ed[i].end != p)
            if(vis[Ed[i].end] || dfs(Ed[i].end , x))
                return 1;
    return 0;
}

void color(int x){
    if(x <= N)
        ans += t++ * pri[x];
    else{
        color(ch[x][0]);
        color(ch[x][1]);
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in" , "r" , stdin);
    //freopen("out" , "w" , stdout);
#endif
    cntNode = N = read();
    size[0] = 1;
    for(int i = 1 ; i <= N ; ++i){
        int a = read();
        addEd(a , i);
        addEd(i , a);
        fa[i] = i;
        size[i] = 1;
    }
    for(int i = 1 ; i <= N ; ++i){
        pri[i] = read();
        q.push(i);
    }
    for(int i = 0 ; i <= N ; ++i)
        if(!vis[i])
            if(dfs(i , -1)){
                puts("-1");
                return 0;
            }
    while(!q.empty()){
        int t = q.top();
        q.pop();
        if(mark[t])
            continue;
        int f = find(pre[t]) , x = ++cntNode;
        fa[f] = fa[t] = fa[x] = x;
        size[x] = size[f] + size[t];
        pri[x] = pri[f] + pri[t];
        ch[x][0] = f;
        ch[x][1] = t;
        pre[x] = pre[f];
        mark[t] = mark[f] = 1;
        if(pre[x] != -1)
            q.push(x);
    }
    color(cntNode);
    cout << ans;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Itst/p/10324807.html