Codeforces Round #268 (Div. 1) 468D Tree(杜教题+树的重心+线段树+set)

题目大意

给出一棵树,边上有权值,要求给出一个1到n的排列p,使得sigma d(i, pi)最大,且p的字典序尽量小。

d(u, v)为树上两点u和v的距离

 

题解:一开始没看出来p需要每个数都不同,直接敲了个轻重边剖分orz,交上去才发现不对

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int,  int> PII;
typedef pair<long long, int> PLI;
const int maxn = 1e5 + 100;
PLI dp[maxn], dp2[maxn];
vector<PII> G[maxn];
int son[maxn];

void dfs(int x, int fa){
    dp[x].se = x;
    dp[x].fi = 0;
    for(auto e : G[x]){
        if(e.fi == fa) continue;
        dfs(e.fi, x);
        if(dp[e.fi].fi + e.se >= dp[x].fi){
            if(dp[e.fi].fi + e.se == dp[x].fi){
                son[x] = dp[x].se < dp[e.fi].se ? son[x] : e.fi;
                dp[x].se = min(dp[x].se, dp[e.fi].se);
            } else {
                son[x] = e.fi;
                dp[x].fi = dp[e.fi].fi + e.se;
                dp[x].se = dp[e.fi].se;
            }
        }
    }
}

void Maintain(LL v, PLI A, PLI& B){
    if(v + A.fi > B.fi){
        B.fi = v + A.fi;
        B.se = A.se;
    } else if(v + A.fi == B.fi){
        B.se = B.se < A.se ? B.se : A.se;
    }
}

void ddfs(int x, int fa, LL v){
    dp2[x].se = x;
    for(auto e : G[x]){
        if(e.fi == fa) continue;
        if(e.fi == son[x]) continue;
        Maintain(e.se, dp[e.fi], dp2[x]);
    }
    if(son[fa] == x){
        Maintain(v, dp2[fa], dp2[x]);
    } else {
        Maintain(v, dp[fa], dp2[x]);
        Maintain(v, dp2[fa], dp2[x]);
    }
    for(auto e : G[x]) if(e.fi != fa) ddfs(e.fi, x, e.se);
}

int main()
{
    int n, x, y, w;
    cin>>n;
    for(int i = 1; i < n; i++){
        cin>>x>>y>>w;
        G[x].push_back({y, w});
        G[y].push_back({x, w});
    }
    dfs(1, 1);
    LL ans = 0;
    vector<int> V;
    ans += dp[1].fi; V.push_back(dp[1].se);
    dp2[1].se = 1;
    for(auto e : G[1]){
        if(e.fi == son[1]) continue;
        Maintain(e.se, dp[e.fi], dp2[1]);
    }
    for(auto e : G[1]) ddfs(e.fi, 1, e.se);
    for(int i = 2; i <= n; i++){
        if(dp[i].fi > dp2[i].fi){
            ans += dp[i].fi;
            V.push_back(dp[i].se);
        } else if(dp[i].fi == dp2[i].fi){
            ans += dp[i].fi;
            V.push_back(dp[i].se < dp2[i].se ? dp[i].se : dp2[i].se);
        } else {
            ans += dp2[i].fi;
            V.push_back(dp2[i].se);
        }
    }
    cout<<ans<<endl;
    for(auto x : V) cout<<x<<" ";

}

 

 

题解2:

如果排列要求都不同的话,实际上求最大值反而好求了

考虑在以任意点x为根,u和v匹配的答案

就是dis(x, u) + dis(x, v) - dis(x, lca(u, v))

如果这个点x是树的重心,我们就可以使得每个匹配的lca都是x,这时就可以得到最优解

所以求最优解还是很容易的

接下来是匹配

匹配实际上并不好做,需要考虑下面几个问题

1、找到树的重心以后,需要把每颗子树的dfs序用线段树维护,然后用线段树查询值最小的点

2、在匹配的过程中,有可能出现如果把u和v匹配之后,后面就无法完成匹配的情况,这时只能用u来匹配最需要匹配的那棵子树

这个理解起来可能比较抽象,写成具体的式子实际上是

令px[i]表示子树i内还有多少个点没有跟其他子树中的点匹配

py[i]表示子树i内还有多少个点没有被(强调被动)其他子树中的点匹配

如果当前还有y个点没进行匹配,那么如果存在一颗子树i, y < px[i] + py[i],实际上就必须要优先考虑i子树了。

因为如果还是按照字典序匹配,就会造成后续的i子树外需要匹配的点的个数,小于i子树内需要被匹配的点的个数,就根本无法完成匹配!

当然如果这个点本身还是在i子树内的话,那么还是可以和其他子树匹配的,(因为y和px[i]同时减1,不影响后续匹配)

3、重心可以和重心本身匹配,这很扯。。但是确实是可行的,需要特殊考虑

 

实现过程的时候,用set来维护px[i] + py[i],如果出现了y < px[i] + py[i],就进行特殊处理。

没出现就按字典序查询匹配。整个过程都要动态维护px[i], py[i]的值

然后线段树维护dfs序不再多说。

 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <set>
#define fi first
#define se second
using namespace std;
const int maxn = 1e5 + 100;
typedef long long LL;
typedef pair<long long, int> PLI;
typedef pair<int, int> PII;
vector<PLI> G[maxn];
set<PII> S;
int son[maxn], sz[maxn], f[maxn], col[maxn], ll[maxn], rr[maxn], no[maxn], ans[maxn], M[maxn];
int tree[maxn*4];
int n, tot;
LL ANS = 0;
void dfs0(int x, int fa){
    sz[x] = 1;
    for(auto e : G[x]){
        if(e.fi == fa) continue;
        dfs0(e.fi, x);
        sz[x] += sz[e.fi];
        son[x] = max(son[x], sz[e.fi]);
    }
    son[x] = max(son[x], n - sz[x]);
}

void dfs(int x, int fa, LL v){
    sz[x] = 1;
    for(auto e : G[x]){
        if(e.fi == fa) continue;
        dfs(e.fi, x, e.se+v);
        sz[x] += sz[e.fi];
    }
    ANS += v;
}

void Insert(int o, int l, int r, int k, int v){
    if(l == r) { tree[o] = v; return; }
    int mid = (l+r)>>1;
    if(k <= mid) Insert(o*2, l, mid, k, v);
    else Insert(o*2+1, mid+1, r, k, v);
    tree[o] = min(tree[o*2], tree[o*2+1]);
}
int Query(int o, int l, int r, int L, int R){
    if(L <= l && r <= R) return tree[o];
    int mid = (l+r)>>1, ans = 1e9;
    if(L <= mid) ans = min(ans, Query(o*2, l, mid, L, R));
    if(R > mid) ans = min(ans, Query(o*2+1, mid+1, r, L, R));
    return ans;
}

void dfs1(int x, int fa, int cc){
    f[x] = ++tot;
    col[x] = cc;
    Insert(1, 1, n, tot, x);
    for(auto e : G[x]){
        if(e.fi == fa) continue;
        dfs1(e.fi, x, cc);
    }
}

void Erase(int col){
    PII x = make_pair(-M[col]-no[col], col);
    S.erase(x);
    if(x.fi+1 < 0) S.insert({x.fi+1, col});
}

int main()
{
    cin>>n;
    for(int i = 1; i < n; i++){
        int x, y, w;
        cin>>x>>y>>w;
        G[x].push_back({y, w});
        G[y].push_back({x, w});
    }
    int X;
    dfs0(1, 1);
    for(int i = 1; i <= n; i++) if(son[i] <= n/2) X = i;
    dfs(X, X, 0);
    cout<<ANS*2<<endl;
    int flag = 0, N = 0;
    for(auto e : G[X]){
        ll[++N] = tot+1;
        dfs1(e.fi, X, N);
        rr[N] = tot;
        S.insert({-2*sz[e.fi], N});
        M[N] = no[N] = sz[e.fi];
    }
    f[X] = ++tot;
    Insert(1, 1, n, tot, X);
    ll[++N] = tot;
    rr[N] = tot;
    col[X] = N;
    S.insert({0, N});
    M[N] = no[N] = 1;
    int l, r;
    for(int i = 1; i <= n; i++){
        auto x = *S.begin();
        int rem = n-i+1;
        if(col[i] != x.se){
            rem--;
            if(rem < -x.fi){
                l = ll[x.se], r = rr[x.se];
                int y = Query(1, 1, n, l, r);
                ans[i] = y;
                Erase(x.se);
                M[x.se]--;
                Insert(1, 1, n, f[y], 1e9);
                Erase(col[i]);
                no[col[i]]--;
                continue;
            }
        }
        l = ll[col[i]], r = rr[col[i]];
        int y = 1e9;
        if(l-1 > 0) y = min(y, Query(1, 1, n, 1, l-1));
        if(r+1 <= n) y = min(y, Query(1, 1, n, r+1, n));
        if(f[i] == n) y = min(i, y);
        Insert(1, 1, n, f[y], 1e9);
        ans[i] = y;
        Erase(col[i]);
        no[col[i]]--;
        Erase(col[y]);
        M[col[y]]--;
    }
    for(int i = 1; i <= n; i++) cout<<ans[i]<<" ";
}

 

转载于:https://www.cnblogs.com/Saurus/p/7077966.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值