300iq Contest 2 J. Jiry Matchings

题目链接

300iq Contest 2 J. Jiry Matchings

题目大意

一棵大小为 \(n\) 的带权树,对于 \(1\leq k\leq n\),求大小为 \(k\) 的最大权匹配。

\(2\leq n\leq 2\times 10^5\)

思路

容易想到朴素 \(DP\)\(f_{x,k,0/1}\) 表示子树 \(x\)\(k\) 对匹配,\(x\) 是否在匹配中,此时匹配的最大权值和。转移:

\[f_{x,k,0}=\max_{i+j=k}\{f_{x,i,0}+\max(f_{son,j,0},f_{son,j,1})\}\\ f_{x,k,1}=\max_{i+j=k}\{f_{x,i,1}+\max(f_{son,j,0},f_{son,j,1}),\;f_{x,i,0}+f_{son,j,0}+w_e\} \]

这大体上来说是 \(Max+\) 卷积的形式,由于匹配的过程可以看作 一个上下界费用流,费用流每次找到的增广路权值是单调的,所以 \(f_{x,*,0/1}\) 关于 \(k\) 必然是一个凸函数,于是考虑通过维护凸包优化 \(DP\) 转移。

纯粹的 \(Max+\) 卷积可以利用闵可夫斯基和,\(O(\min(siz_1+siz_2)\log)\) 启发式合并凸包的差分序列,直接做到 \(O(n\log^2n)\) 的复杂度。不过这里还有 \(0/1\) 这一维,由转移式可以看出 \(1\) 处有凸包按位取 \(\max\) 的操作,这是不能通过差分序列维护的,所以合并凸包只能做到朴素的 \(O(siz_1+siz_2)\)

不过如果我们能够保证每个元素的信息往上走时被合并的次数足够少,也是可行的。

考虑树链剖分,每个元素只在轻重边切换时被合并,即以重链为单位转移凸包。每个节点处理出除了重儿子之外的子树的凸包并,重链上的合并在顶部一次性完成,链的合并可以通过两边分治做到 \(O(\log n)\),而节点的所有轻儿子就是一条重链的顶部,所以在并向父亲时同时完成此操作即可。

对于合并各个轻儿子的信息,可以先将根加入到各个凸包中,然后对儿子们分治合并,分治按照儿子的个数拆分即可,一次操作中每个凸包中的信息都各自被处理了 \(O(\log n)\) 次。

每个节点向上都会遇到 \(O(\log n)\) 次重链和轻重边转换,每次都会被合并 \(O(\log n)\) 次,从而时间复杂度是 \(O(n\log^2 n)\)

实现细节

  • 链的分治合并时要处理匹配的情况,可以同时维护 \(4\) 个凸包,分别表示链的两端是否已匹配时的值,\(2^4\) 枚举情况合并,注意链长极小时的边界情况。
  • “已被匹配” 是强制性的,因此大部分初始值应设为 \(-\infty\),而不是 \(0\)

Code

#include<iostream>
#include<cstring>
#include<vector>
#include<array>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 200022
#define ll long long
#define mat array<array<vector<ll>, 2>, 2>
#define tam array<vector<ll>, 2>
#define fr first
#define sc second
#define Inf 250000000000000
using namespace std;

const vector<ll> forbid = {-Inf};
int head[N], nxt[2*N], to[2*N], w[2*N];
int cnt, siz[N], son[N], edge[N];
tam hull[N];

void init(){ mem(head, -1), cnt = -1; }
void add_e(int a, int b, int c, bool id){
    nxt[++cnt] = head[a], head[a] = cnt, to[cnt] = b, w[cnt] = c;
    if(id) add_e(b, a, c, 0);
}

vector<ll> conv_max(vector<ll> a, vector<ll> b){
    if(a == forbid || b == forbid) return forbid;
    int n = a.size(), m = b.size();
    int ta = 1, tb = 1;
    vector<ll> c = {(n ? a[0] : 0) + (m ? b[0] : 0)};
    per(i,n-1,1) a[i] -= a[i-1];
    per(i,m-1,1) b[i] -= b[i-1];
    while(ta < n || tb < m){
        if(ta < n && (tb >= m || a[ta] > b[tb])) c.push_back(a[ta++]);
        else c.push_back(b[tb++]);
    }
    rep(i,1,n+m-1) c[i] += c[i-1];
    return c;
}

vector<ll> shift(vector<ll> a, ll d){
    if(a == forbid) return forbid;
    vector<ll> ret = {-Inf};
    for(ll k : a) ret.push_back(k+d);
    return ret;
}

vector<ll> pos_max(vector<ll> a, vector<ll> b){
    bool tf1 = a == forbid, tf2 = b == forbid;
    if(tf1 && tf2) return forbid;
    if(tf1) return b;
    if(tf2) return a;
    int n = a.size(), m = b.size();
    a.resize(max(n, m), -Inf);
    rep(i,0,m-1) a[i] = max(a[i], b[i]);
    return a;
}

vector<tam> dat, exd; vector<int> len, exl;

mat merge_chain(int l, int r){
    mat ret;
    ret[0][0] = ret[0][1] = ret[1][0] = ret[1][1] = forbid;
    if(l == r){
        ret[0][0] = dat[l][0], ret[1][1] = dat[l][1];
        return ret;
    }
    int mid = (l+r)>>1;
    mat lft = merge_chain(l, mid), rgt = merge_chain(mid+1, r);
    rep(a,0,1) rep(b,0,1) rep(c,0,1) rep(d,0,1){
        vector<ll> vec = conv_max(lft[a][b], rgt[c][d]);
        ret[a][d] = pos_max(ret[a][d], vec);
        if(b == 0 && c == 0){
            int ta = (l == mid ? 1 : a), td = (mid+1 == r ? 1 : d);
            ret[ta][td] = pos_max(ret[ta][td], shift(vec, len[mid]));
        }
    }
    return ret;
}

tam merge_son(int l, int r){
    if(l == r) return {pos_max(dat[l][0], dat[l][1]), shift(dat[l][0], len[l])};
    int mid = (l+r)>>1;
    tam lft = merge_son(l, mid), rgt = merge_son(mid+1, r);
    return {conv_max(lft[0], rgt[0]), pos_max(conv_max(lft[0], rgt[1]), conv_max(lft[1], rgt[0]))};
}

void dfs(int x, int fa){
    siz[x] = 1; int mx = 0;
    for(int i = head[x]; ~i; i = nxt[i]){
        if(to[i] == fa) continue;
        dfs(to[i], x);
        siz[x] += siz[to[i]];
        if(mx < siz[to[i]]) mx = siz[to[i]], son[x] = to[i], edge[x] = w[i];
    }

    exd.clear(), exl.clear();
    for(int i = head[x]; ~i; i = nxt[i]){
        int y = to[i];
        if(y == fa || y == son[x]) continue;
        dat.clear(), len.clear();
        for(int z = y; z; z = son[z])
            dat.push_back(hull[z]), len.push_back(edge[z]);
        mat val = merge_chain(0, dat.size()-1);
        exd.push_back({pos_max(val[0][0], val[0][1]), pos_max(val[1][0], val[1][1])});
        exl.push_back(w[i]);
    }
    swap(dat, exd), swap(len, exl);

    if(!dat.empty()) hull[x] = merge_son(0, dat.size()-1);
    else hull[x][0] = {0}, hull[x][1] = forbid;
}

int main(){
    ios::sync_with_stdio(false);
    int n; cin>>n;
    int a, b, c; init();
    rep(i,1,n-1) cin>>a>>b>>c, add_e(a, b, c, 1);

    dfs(1, 0);
    dat.clear(), len.clear();
    for(int x = 1; x; x = son[x])
        dat.push_back(hull[x]), len.push_back(edge[x]);

    mat ret = merge_chain(0, dat.size()-1);
    vector<ll> ans = pos_max(pos_max(ret[0][0], ret[0][1]), pos_max(ret[1][0], ret[1][1]));

    rep(i,1,(int)ans.size()-1) cout<< ans[i] <<" ";
    rep(i,1,n-(int)ans.size()) cout<<"? ";
    cout<<endl;
    return 0;
}
posted @ 2022-02-17 21:40  Neal_lee  阅读(135)  评论(0编辑  收藏  举报