bzoj1758: [Wc2010]重建计划【分数规划+点分治+单调队列】

题目大意:

给一棵边带权树,求一条长度在[L,R]中且平均权值最大的链。n<=100000,边权小于1000000.

解题思路:

先分数规划,把每条边边权减去mid,就变成了判断是否有一条长度在[L,R]的链权值大等于0.

考虑点分治,对于一个点 u u ,即是求一个和 u 不同子树的点 v v ,且 Ldep[u]dep[v]Rdep[u],满足 dis[v]+dis[u]0 d i s [ v ] + d i s [ u ] ≥ 0

显然可以用线段树维护 dis d i s 最大值,但这样是 O(nlog3n) O ( n l o g 3 n ) 的,如何线性求出呢?

如果我们按深度从小到大枚举点 u u ,那么 v 的可行区间也在向右移动,就可以用单调队列维护了。

注意子树也要按最大深度从小到大枚举,不然会被卡成 O(n2logn) O ( n 2 l o g n )

还有二分写点分里面比较好,因为可以不停更新二分左边界。

ps:写完后TLE成zz,一个坑填了又有一个坑,上面的注意都是亲历,所以改了好几份代码……

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=200005;
const double eps=1e-9,INF=1e9;
int n,m,L,R,cnt,maxdep;
int tot,first[N],nxt[N<<1],to[N<<1];
int root,totsize,size[N],maxsub[N],vis[N];
int q[N],mx[N],V[N];
double f[N],g[N],w[N<<1],dis[N],ans,mine=INF,maxe;
inline bool cmp(const int &a,const int &b){return mx[a]<mx[b];}
void add(int x,int y,int z)
{
    nxt[++tot]=first[x],first[x]=tot,to[tot]=y,w[tot]=z;
}
bool findroot(int u,int fa)
{
    size[u]=1,maxsub[u]=0;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(v==fa||vis[v])continue;
        findroot(v,u),size[u]+=size[v];
        if(size[v]>maxsub[u])maxsub[u]=size[v];
    }
    maxsub[u]=max(maxsub[u],totsize-size[u]);
    if(maxsub[u]<maxsub[root])root=u;
}
void dfs(int u,int fa,int dep,double d)
{
    cnt=max(cnt,dep),dis[u]=d;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(v==fa||vis[v])continue;
        dfs(v,u,dep+1,d+w[e]);
    }
}
void dfs(int u,int fa,int dep)
{
    g[dep]=max(g[dep],dis[u]),cnt=max(cnt,dep);
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(v==fa||vis[v])continue;
        dfs(v,u,dep+1);
    }
}
bool check(double mid)
{
    for(int i=1;i<=maxdep;i++)f[i]-=mid*i;
    for(int i=1;i<=cnt;i++)g[i]-=mid*i;
    int head=1,tail=0;bool res=false;
    for(int i=cnt,j=0;i&&!res;i--)
    {
        while(j<=R-i&&j<=maxdep)
        {
            while(head<=tail&&f[q[tail]]<=f[j])tail--;
            q[++tail]=j,j++;
        }
        while(head<=tail&&q[head]<L-i)head++;
        if(head<=tail&&g[i]+f[q[head]]>=0)res=true;
    }
    for(int i=1;i<=maxdep;i++)f[i]+=mid*i;
    for(int i=1;i<=cnt;i++)g[i]+=mid*i;
    return res;
}
void solve(int u)
{
    vis[u]=1;maxdep=m=dis[u]=0;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(vis[v])continue;
        V[++m]=v,cnt=0,dfs(v,u,1,w[e]),mx[v]=cnt;
    }
    sort(V+1,V+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        int v=V[i];
        cnt=mx[v],dfs(v,u,1);
        double l=max(mine,ans),r=maxe;
        while(r-l>eps)
        {
            double mid=(l+r)/2;
            if(check(mid))l=mid;
            else r=mid;
        }
        ans=r;
        maxdep=max(maxdep,cnt);
        for(int i=1;i<=cnt;i++)f[i]=max(f[i],g[i]),g[i]=-INF;
    }
    for(int i=1;i<=maxdep;i++)f[i]=-INF;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];if(vis[v])continue;
        root=0,maxsub[0]=totsize=size[v],findroot(v,u);
        solve(root);
    }
}
int main()
{
    //freopen("lx.in","r",stdin);
    //freopen("lx.out","w",stdout);
    n=getint(),L=getint(),R=getint();
    int x,y,z;
    for(int i=1;i<n;i++)
    {
        x=getint(),y=getint(),z=getint();
        add(x,y,z),add(y,x,z);
        mine=min(mine,(double)z),maxe=max(maxe,(double)z);
    }
    for(int i=1;i<=n;i++)g[i]=f[i]=-INF,vis[i]=0;
    root=0,maxsub[0]=totsize=n,findroot(1,0);
    solve(root);
    printf("%0.3lf\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值