peiwenjun's blog 没有知识的荒原

CF1119F Niyaz and Small Degrees 题解

题目描述

给定一棵 \(n\) 个点的树,边有边权。

\(\forall0\le k\le n-1\) ,求删掉的边的权值和最小值,使得每个点的度数 \(\le k\)

数据范围

  • \(2\le n\le 2.5\cdot 10^5\)
  • \(1\le u\neq v\le n,1\le w\le 10^6\)

时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{256MB}\)

分析

先考虑暴力的树形 \(\texttt{dp}\) 做法。固定 \(k\) ,记 \(dp_{u,0/1}\) 表示不删除 \(/\) 删除 \((u,fa_u)\) 这条边, \(u\) 子树的最小代价。

先不考虑 \(u\) 的度数限制,转移方程 \(dp_{u,*}=\sum\limits_{v\in son_u}\min(dp_{v,0},dp_{v,1}+w)\)

为了让 \(u\) 符合度数限制,对于 \(dp_{u,0},dp_{u,1}\) ,我们分别需要删 \(deg_u-k,deg_u-k-1\) 条边。

先全取 \(dp_{v,0}\) ,并且把 \(dp_{v,1}+w-dp_{v,0}\) 丢入小根堆中。

显然贪心取堆中的前若干小最优,注意 \(\lt 0\) 的值一定取。

算上外层枚举 \(k\) 的代价,时间复杂度 \(\mathcal O(n^2\log n)\)


我们并不关心 \(deg_u\le k\) 的点,只需对每个 \(deg_u\gt k\) 的点构成的连通块做树形 \(\texttt{dp}\)

这样时间复杂度有了显著优化:

\[\sum_{k=0}^{n-1}\sum_{u=1}^n[deg_u>k]=\sum_{u=1}^ndeg_u=\mathcal O(n)\\ \]

对每个点 \(u\) 开一个 multiset ,存储所有的 \(x\) 满足可以通过 \(x\) 的代价让 \(deg_x\) 减少 \(1\) ,初始加入所有连向 \(deg\le k\) 的点的权值,

然后对 \(deg\gt k\) 的连通块 \(\texttt{dp}\) ,如果 \(dp_{v,0}<dp_{v,1}+w\) ,则将 \(dp_{v,1}+w-dp_{v,0}\) 插入 multiset

然后一直弹出最大值直到 multiset 大小等于 \(deg_u-k\) ,就可以得到 \(dp_{u,0}\) 的值,\(dp_{u,1}\)还需要多弹一个。

时间复杂度 \(\mathcal O(n\log n)\)

以下是代码实现细节。

从小到大扫描 \(k\) ,这样每次只需删除 \(deg=k\) 的点。扫描它的所有 \(deg\gt k\) 的邻点,并将边权加入 multiset

树形 \(\texttt{dp}\) 结束后,我们需要将 multiset 还原,因此再开两个 vector 记录插入了哪些数和弹出了哪些数, dfs 结束后再逆回去。

为了避免复杂度退化,遍历出边时只能扫描 \(deg\gt k\) 的点,因此我们需要将邻点按 deg 降序排序,扫到 \(deg\le k\) 的点时推出循环。

细节有亿点点多。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=2.5e5+5;
int k,n,u,v,w,cur;
int deg[maxn],vis[maxn];
pii p[maxn];
vector<pii> g[maxn];
multiset<ll> s[maxn];
ll sum[maxn],dp[maxn][2];
bool cmp(pii a,pii b)
{
    return deg[a.fi]>deg[b.fi];
}
#define s s[x]
void add(int x,ll v)
{
    s.insert(v),sum[x]+=v;
}
void del(int x,ll v)
{
    sum[x]-=v,s.erase(s.find(v));
}
#undef s
void dfs(int u)
{
    vis[u]=k;
    int cnt=deg[u]-k;
    while(s[u].size()>cnt) del(u,*--s[u].end());
    ll res=0;
    vector<ll> a,b;
    for(auto p:g[u])
    {
        int v=p.fi,w=p.se;
        if(deg[v]<=k) break;
        if(vis[v]==k) continue;
        dfs(v);
        res+=min(dp[v][0],dp[v][1]+w);
        if(dp[v][0]<dp[v][1]+w)
        {
            ll x=dp[v][1]+w-dp[v][0];
            b.push_back(x),add(u,x);
        }
        else cnt--;
    }
    while(s[u].size()>max(cnt,0))
    {
        auto x=*--s[u].end();
        a.push_back(x),del(u,x);
    }
    dp[u][0]=res+sum[u];
    while(s[u].size()>max(cnt-1,0))
    {
        auto x=*--s[u].end();
        a.push_back(x),del(u,x);
    }
    dp[u][1]=res+sum[u];
    for(auto p:a) add(u,p);
    for(auto p:b) del(u,p);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        g[u].push_back(mp(v,w)),g[v].push_back(mp(u,w));
        deg[u]++,deg[v]++;
    }
    for(int i=1;i<=n;i++)
    {
        vis[i]=-1,p[i]=mp(deg[i],i);
        sort(g[i].begin(),g[i].end(),cmp);
    }
    sort(p+1,p+n+1);
    for(k=0,cur=1;k<=n-1;k++)
    {
        while(cur<=n&&p[cur].fi<=k)
        {
            int u=p[cur++].se;
            for(auto p:g[u])
            {
                if(deg[p.fi]<=k) break;
                add(p.fi,p.se);
            }
        }
        ll res=0;
        for(int i=cur;i<=n;i++)
            if(vis[p[i].se]!=k)
            {
                int u=p[i].se;
                dfs(u),res+=dp[u][0];
            }
        printf("%lld ",res);
    }
    putchar('\n');
    return 0;
}

posted on 2022-08-10 20:36  peiwenjun  阅读(7)  评论(0)    收藏  举报

导航