CodeForces 1119F Niyaz and Small Degrees(树形dp + multiset)

 

 

大致题意:一棵树,每条边都有权值。你可以把一些边给删除,现在问你,当满足所有点的度都小于等于x的时候,删除的边的权值和最小是多少。要求输出x从0到n-1的所有结果。

这题是最近遇到过的最麻烦的题,太菜了,做了好久。。。

一开始的想法,试图去贪心,寻找一种策略能够保证删边的时候权值和最小。然而显然并没有这种策略。然后考虑x从大到小顺序处理,每次只处理度数为当前x的点。对于当前度数为x的点,他们可能会构成连通块,因此我们考虑树形dp。由于x是从大往小枚举的,所以每一步只需要把这些点相连的一条边给删除即可。令dp[i][0]表示处理完i以及i的所有后代的最小权值和。令dp[i][1]表示处理完i的所有后代的最小权值和。可以有转移方程:

                           \large dp[i][0]=min(dp[y][1] - dp[y][0] + w) +\sum dp[son][0])

                                                   \large dp[i][1]=\sum dp[son][0]

这个转移方程看起来很对,实际上它也并没有什么问题。然后,在得出最后结果之后,我又用很麻烦的方法记录下dp选择的方案,然后真的把这些边删除,之后继续往下做。看起来并没有什么问题,然而得到的确是一个WA。

实际上,这样子做违背了dp的基本原则。如果对于一个固定的x来说,这样子做是没有问题的,但是x是变化的。我这样子做相当于每次是在前一步的基础上继续往下做的,前一步对后一部产生了影响,就产生了后效性,所以就GG了。主要的问题就出现在删边的时候,边是肯定不能直接删的。

正解的话,我们考虑x从小到大处理。每次我们只需考虑那些度数大于x的点,还是一样树形dp,转移方程也是类似的,只不过这次不是只删除一条边,而是删除每个点对应度数d[i]-x条边,但是实际上,删边的数量可以大于d[i]-x。具体来说,对于每一个儿子y,如果满足dp[y][1]-dp[y][0]+w<0,那么这条边一定得删除,因为删除这条边会比不删要更优。把所有这些边都删除之后,如果删的边的数目没有达到d[i]-x,那么我们再按照dp[y][1]-dp[y][0]+w排序,删掉前几个小的边即可。

但是呢,如果直接按照这个去做,你会发现会超时。我们考虑一下时间复杂度。如果我们每次只考虑所有度数大于x的点,那么相当于一个度数为d的点,它会被考虑d次。那么整个图的度的和是2*(n-1),对应考虑点该也是2*(n-1),时间复杂度应该是O(N)级别的,理论上不会超时。但是,你再实际操作的时候,由于你不删边,所以你在处理每个点的时候,还是会枚举所有的边。最简单的例子考虑一个菊花图,每次处理中心点的时候,会把所有的儿子都枚举一遍,复杂度就退化到了O(N^2)。

解决的方法就是,我们在边表里面,把边按照儿子的度数去排序。然后由于x是递增的,所以我们维护边表的起始点,每次遍历的时候从起始点开始,不去遍历那些度数小于x的点。但是删边的时候我们还是要用到这些小度数的边,因为这些边也是可以删掉的。所以对于每个点我们还要维护一个multiset,把那些已经考虑过的度数小于当前x的点的w放进去,后面就把每个需要考虑的点的dp[y][1]-dp[y][0]+w放进去,正好起到了排序的作用。当然,最后需要把这些加入的点给删掉,因为下次进来的时候只能初始保留那些度数小于x的点的w。

最后的话,还要注意,对于multiset来说,erase的时候,如果传进去的是一个元素,那么它会把所以的这个元素都删掉。所以当你想要删除一个元素的是,最好是先用find的知道它的迭代器,然后删掉。具体见代码:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3fll
#define LL long long
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define file(x) freopen(#x".in","r",stdin);
using namespace std;

const int N = 3e5 + 10;

multiset<LL,greater<LL> > st[N];
vector<int> d[N],dd[N];
struct Edge{int y,w;};
LL dp[N][2],s[N],now;
int h[N]; bool v[N];
vector<Edge> g[N];

void dfs(int x,int fa)
{
    vector<LL> add,del;
    LL tmp,sum=0; v[x]=1;
    int delta=g[x].size()-now;
    while(h[x]<g[x].size()&&g[g[x][h[x]].y].size()<=now) ++h[x];
    while(st[x].size()>delta) s[x]-=(tmp=*st[x].begin()),st[x].erase(st[x].begin());
    for(int i=h[x];i<g[x].size();i++)
    {
        int y=g[x][i].y;
        int w=g[x][i].w;
        if (v[y]||y==fa) continue;
        dfs(y,x);
        if (dp[y][1]+w<=dp[y][0]) sum+=dp[y][1]+w,--delta;
        else
        {
            s[x]+=(tmp=dp[y][1]+w-dp[y][0]);
            st[x].insert(tmp); sum+=dp[y][0]; add.push_back(tmp);
        }
    }
    dp[x][1]=dp[x][0]=sum;
    while(st[x].size()>max(0,delta))
        s[x]-=tmp=(*st[x].begin()),del.push_back(tmp),st[x].erase(st[x].begin());
    delta--; dp[x][0]=sum+s[x];
    while(st[x].size()>max(0,delta))
        s[x]-=tmp=(*st[x].begin()),del.push_back(tmp),st[x].erase(st[x].begin());
    dp[x][1]=sum+s[x];
    for(LL i:del) st[x].insert(i),s[x]+=i;
    for(LL i:add) st[x].erase(st[x].find(i)),s[x]-=i;
}

bool cmp(Edge a,Edge b)
{
    return g[a.y].size()<g[b.y].size();
}

int main()
{
    LL sum=0;
    int n; sc(n);
    for(int i=1;i<n;i++)
    {
        int x,y,w;
        sccc(x,y,w); sum+=w;
        g[x].push_back(Edge{y,w});
        g[y].push_back(Edge{x,w});
    }
    for(int i=1;i<=n;i++)
    {
        d[g[i].size()].push_back(i);
        sort(g[i].begin(),g[i].end(),cmp);
        for(int j=0;j<g[i].size();j++) dd[j].push_back(i);
    }
    LL ans=0;
    printf("%lld ",sum);
    for(int i=1;i<n;i++)
    {
        now=i; ans=0;
        for(int j:d[i])
            for(auto k:g[j])
            {
                if (v[k.y]) continue;
                st[k.y].insert(k.w); s[k.y]+=k.w;
            }
        for(int j:d[i]) v[j]=1;
        for(int j:dd[i])
        {
            if (v[j]) continue;
            dfs(j,0); ans+=dp[j][0];
        }
        printf("%lld ",ans);
        for(int j:dd[i]) v[j]=0;
    }
    return 0;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值