SPOJ Free TourII(点分治+启发式合并)

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 them crowded 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.

Example

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

Output:
12

Explanation

We choose 2 and 6 as the departure and destination place, so the tour will be 2 -> 3 -> 4 -> 5 -> 6, total interest value = 10 + (-2) + (-1) + 5 = 12
* Added some unofficial case

题解:

设路径起点为根,终点为x的路径,经过黑色结点数量为deep[x],路径长度为dis[x]
考虑解决经过根的路径,递归子树处理其它路径。
依次处理root的每棵子树,处理到子树S的时候,我们需要知道出发点为根,终点在前S-1棵子树子树中,经过t(t<K)个黑点的路径的最长长度,记为mx[t]
则对于子树S的结点x

    mx[t]+dis[x](deep[x]+t<=k)-->ans

mx[t]+dis[x](deep[x]+t≤K)−→−−−updateans


(若根为黑色处理时将K–,处理完恢复)若按照deep倒序处理每个结点,mx指针now按照升序扫,mx[now-1]---> mx[now]mx[now−1]−→−−−updatemx[now]

这样就能得到符合条件的mx[t]的最大值。
然后再考虑用子树S的信息更新mx,将两个数组合并的操作次数max[L1,L2]

可以考虑按照每棵子树deep的升序依次合并子树。从总体来看,由于总边数是n-1,则排序的复杂度nlogn,同时这样启发式合并使得合并的复杂度降为nlogn

参考代码:

#include<bits/stdc++.h>
using namespace std;
#define PI acos(-1.0)
#define pii pair<int,int>
#define mkp make_pair
#define pb push_back
#define fi first
#define se second
#define mod 1000000007
typedef long long ll;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3fll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();}
    return x*f;
}

const int maxn=2e5+10;
int n,m,k,cnt,rt,sum,mx_dep,ans;
int dis[maxn],f[maxn],mx[maxn],tmp[maxn];
int head[maxn],vis[maxn],siz[maxn];
int col[maxn],deep[maxn];
vector<pii> vec;

struct Edge{
    int v,w,nxt;
} edge[maxn<<1];

void addedge(int u,int v,int w)
{
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].nxt=head[u];
    head[u]=cnt++;
}

void getroot(int x,int fa)
{
    siz[x]=1;f[x]=0;
    for(int i=head[x];~i;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(v!=fa && !vis[v])
        {
            getroot(v,x);
            f[x]=max(f[x],siz[v]);
            siz[x]+=siz[v];
        }
    }
    f[x]=max(f[x],sum-siz[x]);
    if(f[x]<f[rt]) rt=x;
}

void getdis(int x,int fa)
{
    mx_dep=max(mx_dep,deep[x]);
    for(int i=head[x];~i;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(!vis[v] && v!=fa)
        {
            deep[v]=deep[x]+col[v];
            dis[v]=dis[x]+edge[i].w;
            getdis(v,x);
        }
    }
}

void getmx(int x,int fa)
{
    tmp[deep[x]]=max(tmp[deep[x]],dis[x]);
    for(int i=head[x];~i;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(!vis[v] && v!=fa) getmx(v,x);
    }
}

void solve(int x,int S)
{
    vis[x]=1; vec.clear();
    if(col[x]) --k;
    for(int i=head[x];~i;i=edge[i].nxt)
    {
        if(!vis[edge[i].v])
        {
            mx_dep=0;
            deep[edge[i].v]=col[edge[i].v];
            dis[edge[i].v]=edge[i].w;
            getdis(edge[i].v,x);
            vec.pb(mkp(mx_dep,edge[i].v));
        }
    }

    sort(vec.begin(),vec.end());

    for(int i=0,t=vec.size();i<t;++i)
    {
        getmx(vec[i].se,x);
        int now=0;
        if(i!=0)
        {
            for(int j=vec[i].fi;j>=0;--j)
            {
                while(now+j<k && now<vec[i-1].fi)
                    ++now,mx[now]=max(mx[now],mx[now-1]);
                if(now+j<=k) ans=max(ans,tmp[j]+mx[now]);
            }
        }
        if(i!=t-1)
        {
            for(int j=0,ct=vec[i].fi;j<=ct;++j)
                mx[j]=max(mx[j],tmp[j]),tmp[j]=0;
        }
        else
        {
            for(int j=0,ct=vec[i].fi;j<=ct;++j)
            {
                if(j<=k) ans=max(ans,max(mx[j],tmp[j]));
                tmp[j]=mx[j]=0;
            }
        }
    }

    if(col[x]) ++k;

    for(int i=head[x];~i;i=edge[i].nxt)
    {
        if(!vis[edge[i].v])
        {
            rt=0;
            sum=siz[edge[i].v];
            if(siz[edge[i].v]>siz[x]) sum=S-siz[x];
            getroot(edge[i].v,x);
            solve(rt,sum);
        }
    }
}

int main()
{
    n=read();k=read();m=read();
    int color,x,y,z;
    ans=mx_dep=cnt=0; sum=n; f[0]=n;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(col,0,sizeof(col));

    for(int i=1;i<=m;++i) color=read(),col[color]=1;
    for(int i=1;i<n;++i)
    {
        x=read();y=read();z=read();
        addedge(x,y,z);
        addedge(y,x,z);
    }

    getroot(1,0);
    solve(rt,sum);

    printf("%d\n",ans);

    return 0;
}

原文地址:https://www.cnblogs.com/songorz/p/10926542.html

时间: 2024-07-30 23:56:38

SPOJ Free TourII(点分治+启发式合并)的相关文章

启发式合并(堆、set、splay、treap)/线段树合并学习小记

启发式合并 刚听到这个东西的时候,我是相当蒙圈的.特别是"启发式"这三个字莫名的装逼,因此之前一直没有学. 实际上,这个东西就是一个SB贪心. 以堆为例,若我们要合并两个堆a.b,我们有一种极其简单的做法:那就是比较一下它们的大小,将小的堆的每个元素依次插入到大的堆中.不妨设\(|a|≤|b|\),则时间复杂度即为:\(O(|a|*log_2(|a|+|b|))\). 这个东西看似很慢,但当点数较小的时候,我们可以证明复杂度是可被接受的. 比如我们要合并n个堆,这n个堆共有m个点.设这

CodeForces 958F3 Lightsabers (hard) 启发式合并/分治 多项式 FFT

原文链接http://www.cnblogs.com/zhouzhendong/p/8835443.html 题目传送门 - CodeForces 958F3 题意 有$n$个球,球有$m$种颜色,分别编号为$1\cdots m$,现在让你从中拿$k$个球,问拿到的球的颜色所构成的可重集合有多少种不同的可能. 注意同种颜色球是等价的,但是两个颜色为$x$的球不等价于一个. $1\leq n\leq 2\times 10^5,\ \ \ \ \ 1\leq m,k\leq n$. 题解 来自Hel

【主席树启发式合并】【P3302】[SDOI2013]森林

Description 给定一个 \(n\) 个节点的森林,有 \(Q\) 次操作,每次要么将森林中某两点联通,保证操作后还是个森林,要么查询两点间权值第 \(k\) 小,保证两点联通.强制在线. Limitation \(1~\leq~n,~Q~\leq~80000\) Solution 考虑有连边还有查询链上第 \(k\) 大,于是要么用 LCT,要么用主席树. 考虑如果用 LCT 的话,并不能快速的维护两点间链的信息(其实感觉在access的时候乱搞一下有希望在多一个 \(\log\) 的

CodeForces 600E. Lomsat gelral【树上启发式合并】

传送门 好像大家都是拿这道题作为树上启发式合并的板子题. 树上启发式合并,英文是 dsu on tree,感觉还是中文的说法更准确,因为这个算法和并查集(dsu)没有任何关系.一般用来求解有根树的所有子树的统计问题. 根据轻重儿子的各种性质,可以证明这个算法的时间复杂度为 \(O(nlogn)\),虽然看起来暴力的不行,但是却是一个很高效的算法. 算法的核心其实就是对于每个节点,先计算轻儿子,再计算重儿子,把自己和轻儿子的所有贡献累计到重儿子上去,如果自己是轻儿子,就把贡献清空. 如果掌握了树链

【Splay】【启发式合并】hdu6133 Army Formations

题意:给你一颗树,每个结点的儿子数不超过2.每个结点有一个权值,一个结点的代价被定义为将其子树中所有结点的权值放入数组排序后,每个权值乘以其下标的和.让你计算所有结点的代价. 二叉树的条件没有用到. 每个结点开一个Splay,从叶子往上启发式合并上去,可以先bfs一遍确定合并顺序.每一次将Splay大小较小的结点的权值全提取出来塞到较大的里面. 由于权值可能重复出现,所以每个结点记个cnt. 答案统计的时候,就将那个刚塞进去的旋到根,然后答案加上左子树的权值和,再加上(右子树的权值的个数+该结点

【BZOJ2733】永无乡[splay启发式合并or线段树合并]

题目大意:给你一些点,修改是在在两个点之间连一条无向边,查询时求某个点能走到的点中重要度第k大的点.题目中给定的是每个节点的排名,所以实际上是求第k小:题目求的是编号,不是重要度的排名.我一开始差点被这坑了. 网址:http://www.lydsy.com/JudgeOnline/problem.php?id=2733 这道题似乎挺经典的(至少我看许多神犇很早就做了这道题).这道题有两种写法:并查集+(splay启发式合并or线段树合并).我写的是线段树合并,因为--splay不会打+懒得学.

学习笔记::启发式合并

昨天做Tree Rotation,没发现自己写的是暴力,还要了数据...... 然后发现好像必须得用启发式合并 不想学线段树,学了个splay的 --------------------------------------------------- 假设现在有n个点,每个点是一个splay,互不连起来 假设我们每次让两个不连通的splay联通, 所谓启发式:就是把小的合并到大的上,这样使复杂度有保证 怎么合并呢?就是先把小的splay的每个点找出来,然后插入到大的splay ----------

BZOJ 2733: [HNOI2012]永无乡(treap + 启发式合并 + 并查集)

不难...treap + 启发式合并 + 并查集 搞搞就行了 ---------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define rep(i, n) for(int i = 0; i &l

[BZOJ 1483][HNOI 2009]梦幻补丁(有序表启发式合并)

题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1483 分析: 先将不同的颜色的出现位置从小到大用几条链表串起来,然后统计一下答案 对于每次修改,修改一下答案即可,修改之后需要将两个颜色的链表合并就行了,但感觉似乎会TLE? 以下摘录与Hzwer的blog: 1:将两个队列合并,有若干队列,总长度为n,直接合并,最坏O(N), 2:启发式合并呢? 每次我们把短的合并到长的上面去,O(短的长度) 咋看之下没有多大区别, 下面让我们看看