巡游tour {二分+树分治+单调队列}

  • 【题目描述】
    Tar国正在准备每年一次的巡游活动。国王将会在一个城市S里召集人群,沿着城市间的道路进行游览,最终在一个城市T里发表他每年一次的著名演讲。
    Tar国有N个城市,由于国家的特殊要求,每两个城市之间存在一条唯一的简单通路。 国王希望借着这个机会视察Tar国的城市建设,因此他提出S到T的距离不能少于L条道路。 同时,国王的私人医生检查了他的身体情况后,断定国王的身体不适合做长途旅行,因此他要求S到T的距离不能多于R条道路。
    另外,政府希望跟随国王的人民沿途不仅能看到城市风景,还能看到城市外的美丽乡村。因此每条道路定义了一个魅力值Ci,一条路径的魅力值定义为这条路径的中位数。更详细的说法是这样的: 将路径上所有边的魅力值排序,得到序列{Ai}。假设i=2k+c(0<=c<=1),中位数就是A(k+1)。 你的任务就是求出魅力值最大的路径,并输出这个魅力值。

  • 【Sample Input】
    (第一行是三个整数N,L,R,表示Tar国的城市个数、路径的最小和最大长度。
    接下来N-1行,每行3个整数Ai,Bi,Ci,表示有一条连接Ai和Bi且魅力值Ci的道路。)
    {样例输入1}
    5 1 4
    1 2 1
    1 3 4
    3 4 7
    3 5 2
    {样例输入2}
    6 3 4
    1 2 1
    2 3 1
    3 4 1
    4 5 2
    5 6 2

  • 【Sample Output】
    {样例输出1} 7
    {样例输出2} 2

  • 【数据范围】
    {20%} N<=100。
    {65%} N<=30000。这部分数据的时间限制是2s,其余数据是4s。
    存在着大约20%的数据,树的每个节点度不超过2。
    {100%}N<=10^5,1<=L<=R<=N-1, 1<=Ci<=10^9


【题解】二分+树分治+单调队列
题目大意就是要求一条路径使得其中位数最大。首先由于答案很难直接确定,采用二分~
{20%} 首先二分出可能答案mid。有了mid值之后对于每条边w[i],若w[i]>=mid,令w[i]=1,else w[i]=-1
这样一来,问题被转化为树上是否存在一条长度在[L,R]之间的非负路径,暴力判断即可;
{65%} 二分+树分治+线段树:
依然如20%做法找非负路径。但这里采用树分治的方法。对于每棵子树我们只考虑经过根的路径,其余递归处理。
考虑过根的路径。对于当前递归到的子树x,依次处理其儿子子树的信息并与之前儿子子树得到的信息算出答案。那么我们要保存的值是什么呢?
如果我们对于每个节点保存一个深度de[i],从点i到当前根节点的路径值为sum[x],那么对于已处理过的所有节点的深度相同的点,只需要保存路径最大值即可。因为相同深度的点对答案贡献的范围是一样的(都在[L,R]之间),而要找非负路径显然最大值更优。
得到这个性质之后,考虑怎么更新答案。遍历当前子树,对于每个节点u,合法的答案就是sum[u]+max(p[v]) 其中p[v]代表深度为v的路径最大值,v代表对于当前点距离范围在[L,R]之间的深度。而区间最大值,我们显然能用线段树来做。这样每次树分治我们构造一棵线段树,复杂度为 O(log N),树分治复杂度为 O(N log N),二分复杂度为 O(log N)。总时间复杂度:O(N log N^3)。
{100%} 二分+树分治+单调队列
在60%的基础上,我们思考能否降低查询需求区间最大值的复杂度。如果对于当前子树遍历时使用广搜,那么深度显然是逐渐增大的,此时我们可以用单调队列来记录最大值。
但是这个方法要注意,单调队列里保存的最多是当前子树的最大深度个节点,所以如果处理的第一棵子树很大的话,复杂度容易退化。为了保证复杂度,可以在二分前先树分治做一遍预处理,使得子树按照深度从小到大排序。
时间复杂度:O(N log N^2);
//但是我懒,我不想打初始化,所以我树分治加了个剪枝过的。如果当前子树大小小于L就返回。


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define N 100005
#define inf 1000000000
struct edge{ int to,nxt,s,w;}e[N<<1];
int n,L,R,cnt,ans,m,mx,rt,tot,l=1,r,w[N],fi[N],s[N],p[N],q[N],f[N],g[N],de[N],fa[N];
bool bo[N];
    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)
    {
        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[x])
            {
                fa[e[i].to]=x;
                findrt(e[i].to);
                s[x]+=s[e[i].to];
                mxx=std::max(mxx,s[e[i].to]);
            }
        mxx=std::max(mxx,tot-s[x]);
        if (mxx<mx) mx=mxx,rt=x;
    }
    bool work(int x)
    {
        int mxdp=0,h,t,he,ta;f[0]=0;
        for (int i=fi[x];i;i=e[i].nxt)
        {
            if (bo[e[i].to]) continue;
            h=1;t=0;
            for (int j=mxdp;j>=L;--j)
            {
                for (;h<=t && f[p[t]]<=f[j];--t);
                p[++t]=j;                   
            }
            de[e[i].to]=1;g[1]=e[i].w;fa[e[i].to]=x;
            for (q[he=ta=1]=e[i].to;he<=ta;++he)
            {
                for (;h<=t && de[q[he]]+p[h]>R;++h);
                if (de[q[he]]<=L)
                {
                    for (;h<=t && f[p[t]]<=f[L-de[q[he]]];--t);
                    p[++t]=L-de[q[he]];
                }
                if (h<=t && f[p[h]]+g[he]>=0) return true;
                if (de[q[he]]>=R) continue;
                for (int j=fi[q[he]];j;j=e[j].nxt)
                {
                    if (e[j].to==fa[q[he]] || bo[e[j].to]) continue;
                    fa[e[j].to]=q[he];
                    de[q[++ta]=e[j].to]=de[q[he]]+1;
                    g[ta]=g[he]+e[j].w;
                }
            }
            mxdp=std::max(mxdp,de[q[ta]]);
            for (int j=1;j<=ta;++j) f[de[q[j]]]=std::max(f[de[q[j]]],g[j]);
        }
        for (int i=0;i<=mxdp;++i) f[i]=-inf;
        return false;
    }
    bool dfs(int x,int z)
    {
        if (s[x]>=L && work(x)) return true;
        for (int i=fi[x];i;i=e[i].nxt)
            if (!bo[e[i].to])
            {
                if (s[e[i].to]>s[x]) s[e[i].to]=z-s[x];
                tot=mx=s[e[i].to];rt=0;
                findrt(e[i].to);bo[rt]=true;
                if (dfs(rt,s[e[i].to])) return true;
            }
        return false;
    }
    bool check(int mid)
    {
        for (int i=0;i<=n;++i) bo[i]=false,fa[i]=0,f[i]=-inf; 
        mx=n;tot=n;findrt(1);
        bo[rt]=true;
        for (int i=1;i<=cnt;++i)
            if (e[i].s>=mid) e[i].w=1;
            else e[i].w=-1;
        return (dfs(rt,n));
    }
int main()
{
    scanf("%d%d%d\n",&n,&L,&R);
    for (int i=1;i<n;++i) 
    {
        int u,v;scanf("%d%d%d\n",&u,&v,&w[i]);
        add(u,v,w[i]);add(v,u,w[i]);
    }
    std::sort(w+1,w+n);
    mx=tot=n;findrt(1);ans=-1;
    for (int i=1;i<=n;++i) f[i]=-inf;
    for (r=n-1;l<=r;)
    {
        int mid=(l+r)>>1;
        if (check(w[mid])) l=mid+1,ans=w[mid];
        else r=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}

【题外话】我真是。。太菜了。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值