【spoj 1825】Free tour II

  • 【Problem】
    After the success of 2nd anniversary (take a look at problem FTOUR for more details), this 3rd year, Travel Agent SPOJ goes on with another discount tour.
    The tour will be held on ICPC island, a miraculous one on the Pacific Ocean. We list N places (indexed from 1 to N) where the visitors can have a trip. Each road connecting them has an interest value, and this value can be negative (if there is nothing interesting to view there). Simply, these N places along with the roads connecting them form a tree structure. We will choose two places as the departure and destination of the tour.
    Since September is the festival season of local inhabitants, some places are extremely crowded (we call themcrowded places). Therefore, the organizer of the excursion hopes the tour will visit at most K crowded places(too tiring to visit many of them) and of course, the total number of interesting value should be maximum.
    Briefly, you are given a map of N places, an integer K, and M id numbers of crowded place. Please help us to find the optimal tour. Note that we can visit each place only once (or our customers easily feel bored), also the departure and destination places don’t need to be different.

    【Input】
    There is exactly one case. First one line, containing 3 integers N K M, with 1 <= N <= 200000, 0 <= K <=M, 0 <= M <= N.
    Next M lines, each line includes an id number of a crowded place.
    The last (N – 1) lines describe (N – 1) two-way roads connected N places, form a b i, with a, b is the id of 2 places, and i is its interest value (-10000 <= i <= 10000).

    【Output】
    Only one number, the maximum total interest value we can obtain.

    【Sample Input】
    8 2 3
    3
    5
    7
    1 3 1
    2 3 10
    3 4 -2
    4 5 -1
    5 7 6
    5 6 5
    4 8 3

    【Sample Output】
    12

  • 【题目大意】
    给出一棵树,树上节点黑白染色。问树上的最长的至多经过k个黑点的路径。


  • 【题解一】点分治+线段树 { O( nlog^2n ) }
    按照树分治的基本思路,将问题分解为两部分:
    (1)所求的路径经过当前树的根节点;
    (2)所求路径在当前根节点的一棵子树内;
    并且显然情况二可以用分解为情况一的方法分解决。
    考虑经过根节点的路径。显然,路径被根节点分为两部分,分别在不同的两棵子树中。我们记 f[i] 为已经处理过的子树中路径上有 i 个黑色节点时的路径长度最大值,当处理当前子树时,先处理出子树内的各节点到根节点的路径长度dis及经过黑点个数num,这样可以用子树中节点更新答案:
    ans=max( f[i] )+dis {0<=i<=k-num}
    更新答案后,再将子树信息与之前子树信息合并。
    那么问题来了(⊙ o ⊙),怎样合并更高效呢???
    很显然地会想到线段树,存储、查询区间最大值。所以这个问题就解决啦。
#include <cstdio>
#include <iostream>
#include <cstring>
#define inf 2147483647
#define maxn 200005
struct edge{ int to,nxt,s;}e[maxn*2];
struct point{ int num,dis;}q[maxn];
int n,k,m,cnt,tot,rt,mx,ans,fi[maxn],s[maxn],num[maxn],dis[maxn],f[maxn*8];
bool b[maxn],bo[maxn];
    void add(int u,int v,int w)
    {
        e[++cnt].to=v;e[cnt].s=w;
        e[cnt].nxt=fi[u];fi[u]=cnt;
    }
    void findrt(int x,int fa)
    {
        s[x]=1;int mxx=0;
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to] && e[i].to!=fa)
            {
                findrt(e[i].to,x);
                s[x]+=s[e[i].to];
                mxx=std::max(mxx,s[e[i].to]);
            }
        mxx=std::max(mxx,s[x]-mxx);
        if (mxx<mx) mx=mxx,rt=x;
    }
    void dfs(int x,int fa)
    {
        q[++q[0].num].num=num[x];
        q[q[0].num].dis=dis[x];
        for (int i=fi[x];i;i=e[i].nxt)
            if (e[i].to!=fa && !bo[e[i].to])
            {
                num[e[i].to]=num[x]+b[e[i].to];
                dis[e[i].to]=dis[x]+e[i].s;
                dfs(e[i].to,x);
            }
    }
    int query(int k,int l,int r,int x,int y)
    {
        if (x<=l && r<=y) return f[k];
        int mid=(l+r)>>1,s=k+k,ans=-inf;
        if (x<=mid) ans=std::max(ans,query(s,l,mid,x,y));
        if (y>mid) ans=std::max(ans,query(s+1,mid+1,r,x,y));
        return ans;     
    }
    void modify(int k,int l,int r,int x,int y)
    {
        f[k]=std::max(f[k],y);
        if (l==r) return;
        int mid=(l+r)>>1,s=k+k;
        if (x<=mid) modify(s,l,mid,x,y);
        else modify(s+1,mid+1,r,x,y);
    }
    void dp(int x)
    {
        bo[x]=true;
        memset(f,0,sizeof(f));
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to])
            {
                num[e[i].to]=b[e[i].to]+b[x];
                dis[e[i].to]=e[i].s;
                q[0].num=0;dfs(e[i].to,x);
                for (int j=1;j<=q[0].num;++j)
                    ans=std::max(ans,query(1,0,n,0,k-q[j].num)+q[j].dis);
                for (int j=1;j<=q[0].num;++j)
                    modify(1,0,n,q[j].num,q[j].dis);
            }
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to])
            {
                mx=tot=s[e[i].to];rt=0;
                findrt(e[i].to,0);
                dp(e[i].to);
            }
    }
int main()
{
    scanf("%d%d%d\n",&n,&k,&m);
    for (int i=1;i<=m;++i)
    {
        int x;
        scanf("%d\n",&x);
        b[x]=true;
    }
    for (int i=1;i<n;++i)
    {
        int u,v,w;
        scanf("%d%d%d\n",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    tot=mx=n;rt=0;
    findrt(1,0);
    ans=-inf;dp(rt);printf("%d\n",ans);
    return 0;
} 

  • 【题解二】点分治+启发式合并 { O( nlogn ) }
    如果不用线段树的话,合并时直观看就是暴力吧 { 暴力啊暴力 }
    计算一下暴力的复杂度的话,按照上面算法里的思想,我们记 max[x] 为根为 x 的树上到根最多经过的黑点数量,可以知道每次计算或合并当前子树和已处理子树的复杂度为:
    max ( max[x], max[son] ),其中 son 为当前处理子树。
    而这就是为什么暴力会被卡到 O( n^2 );
    所以在思考优化的时候,肯定是要使得 max ( max[x], max[son] ) 的值最小。于是忽然就想到了某些神奇的东西——
    ~( ≧ ▽ ≦ )/~貌似我们可以先 sort 一下保证当前的 max 值达到最小?于是就降低了复杂度。由于总边数是n-1,排序的复杂度为 O( nlogn ),而这样启发式合并使得暴力的复杂度降为 O( nlogn )。
    于是同样能够解决问题 O(∩_∩)O~~
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define inf 2147483647
#define maxn 200005
using namespace std;
struct edge{ int to,nxt,s;}e[maxn*2];
struct point{ int num,mx;}q[maxn];
int n,k,m,cnt,tot,rt,mx,ans,fi[maxn],s[maxn],num[maxn],dis[maxn],mxx[maxn],tmp[maxn];
bool b[maxn],bo[maxn];
    int read()
    {
        int x=0,f=1;char ch=getchar();
        for (;ch<'0' || ch>'9';){ if(ch=='-')f=-1;ch=getchar();}
        for (;ch>='0' && ch<='9';){ x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    void add(int u,int v,int w)
    {
        e[++cnt].to=v;e[cnt].s=w;
        e[cnt].nxt=fi[u];fi[u]=cnt;
    }
    void findrt(int x,int fa)
    {
        s[x]=1;int mxx=0;
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to] && e[i].to!=fa)
            {
                findrt(e[i].to,x);
                s[x]+=s[e[i].to];
                mxx=max(mxx,s[e[i].to]);
            }
        mxx=max(mxx,tot-mxx);
        if (mxx<mx) mx=mxx,rt=x;
    }
    void dfs(int x,int fa)
    {
        mx=max(mx,num[x]);
        for (int i=fi[x];i;i=e[i].nxt)
            if (e[i].to!=fa && !bo[e[i].to])
            {
                num[e[i].to]=num[x]+b[e[i].to];
                dis[e[i].to]=dis[x]+e[i].s;
                dfs(e[i].to,x);
            }
    }
    void getmx(int x,int fa)
    {
        tmp[num[x]]=max(tmp[num[x]],dis[x]);
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to] && e[i].to!=fa) getmx(e[i].to,x);
    }
    bool comp(point x,point y){ return x.mx<y.mx;}
    void dp(int x)
    {
        bo[x]=true;q[0].num=0;
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to]) 
            {
                mx=0;
                num[e[i].to]=b[e[i].to];
                dis[e[i].to]=e[i].s;
                dfs(e[i].to,x);
                q[++q[0].num].num=e[i].to;
                q[q[0].num].mx=mx;
            }
        sort(q+1,q+q[0].num+1,comp);
        k-=b[x];
        for (int i=1;i<=q[0].num;++i)
        {
            getmx(q[i].num,x);
            int now=0;
            for (int j=q[i].mx;i && j;--j)
            {
                for (;now+j<k && now<q[i-1].mx;)
                    mxx[++now]=max(mxx[now],mxx[now-1]);
                if (now+j<=k) ans=max(ans,mxx[now]+tmp[j]);
            }
            if (i<q[0].num)
                for (int j=0;j<=q[i].mx;++j)
                    mxx[j]=max(mxx[j],tmp[j]),tmp[j]=0;
            else
                for (int j=0;j<=q[i].mx;++j)
                {
                    if (j<=k) ans=max(ans,max(tmp[j],mxx[j]));
                    tmp[j]=mxx[j]=0;
                }
        }
        k+=b[x];
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to])
            {
                mx=tot=s[e[i].to];rt=0;
                findrt(e[i].to,0);
                dp(e[i].to);
            }
    }
int main()
{
    n=read();k=read();m=read();
    for (int i=1;i<=m;++i)
    {
        int x=read();
        b[x]=true;
    }
    for (int i=1;i<n;++i)
    {
        int u=read(),v=read(),w=read();
        add(u,v,w);add(v,u,w);
    }
    tot=mx=n;rt=0;
    findrt(1,0);
    ans=0;dp(rt);printf("%d\n",ans);
    return 0;
} 

  • 【题外话】
    不得不说 spoj 的时间卡的死死地死死地。。。而且还被墙了上不去。总而言之想交个题目都十分艰辛。并且 RE 和 TLE 卡半天。。。{ 已倒台 }
    无论怎样树分治最重要还是多写一些题目吧,把模板打熟练省去更多调试时间,还有一些基本的做题思路整理好,注意细节(⊙o⊙)。毕竟程序长长的QAQ。
洛谷的SPOJ需要注册一个SPOJ账号并进行绑定才能进行交题。您可以按照以下步骤进行注册: 1. 打开洛谷网站(https://www.luogu.com.cn/)并登录您的洛谷账号。 2. 在网站顶部导航栏中找到“题库”选项,将鼠标悬停在上面,然后选择“SPOJ”。 3. 在SPOJ页面上,您会看到一个提示,要求您注册SPOJ账号并进行绑定。点击提示中的链接,将会跳转到SPOJ注册页面。 4. 在SPOJ注册页面上,按照要求填写您的用户名、密码和邮箱等信息,并完成注册。 5. 注册完成后,返回洛谷网站,再次进入SPOJ页面。您会看到一个输入框,要求您输入刚刚注册的SPOJ用户名。输入用户名后,点击“绑定”按钮即可完成绑定。 现在您已经成功注册并绑定了SPOJ账号,可以开始在洛谷的SPOJ题库上刷题了。祝您顺利完成编程练习!\[1\]\[2\] #### 引用[.reference_title] - *1* *3* [(洛谷入门系列,适合洛谷新用户)洛谷功能全解](https://blog.csdn.net/rrc12345/article/details/122500057)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [luogu p7492 序列](https://blog.csdn.net/zhu_yin233/article/details/122051384)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值