【SPOJ1825】Free Tour II-点分治+桶排序

测试地址:Free Tour II
题目大意:给定一棵树,边有边权,点分为黑点和白点,求经过不超过K个黑点的路径的最大边权和。
做法:本题需要用到点分治+桶排序。
首先看到求这样的路径问题,如果DP看上去非常难做,就考虑用点分治。
对于点分治中每个块的根节点,我们考虑求经过这个根节点的合法路径的最大边权和。对于块中的每个点我们得到了一个数对(x,y),其中x为从根到该点路径上经过的黑点数(为了方便,这里不包括根节点),y为路径和。那么我们维护一个数组h,其中h(i)为经过不超过i个点的路径的最大边权和,并且路径一端在根节点,另一端在已经处理过的点中。然后对于正在处理的块中的每一个点,我们实际上就是要求,在x1+x2+black(root)k的条件下,h(x1)+y2的最大值,这个我们就可以用两个指针求出了。
然而注意到,上述算法的复杂度最快也是O(nlog2n)的,再看看数据范围和这个OJ的名字……没办法,优化吧。
优化的方法是,我们把要处理的块按照大小从小到大排序,显然经过的黑点数量不超过max(size),那么我们把各种排序用桶排序解决,那么每一块的时间复杂度就是O(max(size))的,又因为我们已经将块按照大小从小到大排序,所以这个步骤的总时间复杂度就是O(size),这样我们就把算法的时间复杂度降为O(nlogn)了,可以通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,k,m,tot=0,first[200010]={0};
int siz[200010],mxson[200010],q[200010],top;
int srt[200010]={0},nex[200010]={0},black[200010]={0};
ll ww[200010]={0},nowblock[200010]={0},past[200010]={0},ans=0;
bool vis[200010]={0};
struct edge
{
    int v,next;
    ll w;
}e[400010];

void insert(int a,int b,ll w)
{
    e[++tot].v=b;
    e[tot].w=w;
    e[tot].next=first[a];
    first[a]=tot;
}

void dp(int v,int f)
{
    q[++top]=v;
    siz[v]=1,mxson[v]=0;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=f&&!vis[e[i].v])
        {
            dp(e[i].v,v);
            mxson[v]=max(mxson[v],siz[e[i].v]);
            siz[v]+=siz[e[i].v];
        }
}

int find(int v)
{
    int ans=1000000000,ansp=0;
    top=0;
    dp(v,0);
    for(int i=1;i<=top;i++)
    {
        if (max(siz[v]-siz[q[i]],mxson[q[i]])<ans)
            ans=max(siz[v]-siz[q[i]],mxson[q[i]]),ansp=q[i];
    }
    return ansp;
}

void getdown(int v,int f,int passed,ll x)
{
    nowblock[passed]=max(nowblock[passed],x);
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=f&&!vis[e[i].v])
            getdown(e[i].v,v,passed+black[e[i].v],x+e[i].w);
}

int solve(int v)
{
    v=find(v);
    vis[v]=1;
    int blocksiz=1,pastmxsiz=0;
    for(int i=first[v];i;i=e[i].next)
        if (!vis[e[i].v])
        {
            siz[e[i].v]=solve(e[i].v);
            ww[e[i].v]=e[i].w;
        }
    for(int i=first[v];i;i=e[i].next)
        if (!vis[e[i].v])
        {
            nex[e[i].v]=srt[siz[e[i].v]];
            srt[siz[e[i].v]]=e[i].v;
            blocksiz+=siz[e[i].v];
        }
    for(int i=0;i<=blocksiz;i++)
        while (srt[i])
        {
            getdown(srt[i],0,black[srt[i]],ww[srt[i]]);
            for(int j=0;j<=i;j++)
            {
                if (j>0) nowblock[j]=max(nowblock[j-1],nowblock[j]);
                if (k-j-black[v]>pastmxsiz) ans=max(ans,nowblock[j]+past[pastmxsiz]);
                else if (k-j-black[v]>=0) ans=max(ans,nowblock[j]+past[k-j-black[v]]);
            }
            for(int j=0;j<=i;j++)
            {
                past[j]=max(past[j],nowblock[j]);
                if (j>0) past[j]=max(past[j],past[j-1]);
                nowblock[j]=0;
            }
            pastmxsiz=i;
            srt[i]=nex[srt[i]];
        }
    for(int i=0;i<=blocksiz;i++)
        srt[i]=past[i]=0;
    vis[v]=0;
    return blocksiz;
}

int main()
{
    scanf("%d%d%d",&n,&k,&m);
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        black[x]=1;
    }
    for(int i=1;i<n;i++)
    {
        int a,b;ll w;
        scanf("%d%d%lld",&a,&b,&w);
        insert(a,b,w),insert(b,a,w);
    }

    solve(1);
    printf("%lld",ans);

    return 0;
}
posted @ 2018-04-02 22:28  Maxwei_wzj  阅读(93)  评论(0编辑  收藏  举报