bzoj 1758: [Wc2010]重建计划 (01分数规划+点分治)

题目描述

传送门

题解

这道题一直在TLE,但是bzoj发过来的数据都可以在1s内出解,不是很懂为什么。
要最大化所选边的平均值,这是个01分数规划的问题,我们只需要二分答案然后判断树中是否有一条长度在[L,U]之间的链 seval[s]mid 大于0即可。
在点分治中01分数规划的效率要高于在外层01分数规划,因为这样不用每次check的时候都找重心。
还有如果一个点子树的 size<L ,那么就没必要计算了。
在统计一个点的答案的时候,注意并不是每次上届都是U,应该是min(U,size[i])这样可以减少很多不必要的运算。
因为合成的一条路径不能是出自一个点的同一棵子树,所以我们为何一个mx[i]数组表示的是深度为i的已经计算过的点的路径最大值,算当前子树的时候用bfs进行遍历,这样可以保证长度单调不降,那么在统计答案的时候就可以为何一个单调递减的单调栈,线性的更新答案。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 200003
#define eps 1e-5
using namespace std;
int n,point[N],son[N],nxt[N],v[N],tot,q[N],head,tail,size[N],f[N],root,ti,L,U,cnt,top,sz;
double len[N],mx[N],p[N],f1[N],v1,K,ans;
bool vis[N];
struct data{
    int now,x,f; double y;
    data(int No=0,int X=0,int F=0,double Y=0){
        now=No,x=X,f=F,y=Y;
    }
}a[N],st[N];
int cmp(data a,data b)
{
    return a.x<b.x;
}
int get(){
    char ch=getchar();int x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';ch=getchar();
    }return x;
}
void add(int x,int y,double z)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; len[tot]=z;
    tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; len[tot]=z;
}
void getroot(int x,int fa)
{
    size[x]=1; f[x]=0;
    for (int i=point[x];i;i=nxt[i]){
        if (v[i]==fa||vis[v[i]]==1) continue;
        getroot(v[i],x);
        size[x]+=size[v[i]];
        f[x]=max(f[x],size[v[i]]);
    }
    f[x]=max(f[x],tot-size[x]);
    if (f[x]<f[root]) root=x;
}
void get_deep(int x,int fa,int dep,double l)
{
    int s=0,t=0;
    a[++t]=data(x,dep,fa,l);
    while (s<t) {
        ++s;
        int now=a[s].now; int f=a[s].f;
        for (int i=point[now];i;i=nxt[i]){
            if (vis[v[i]]==1||v[i]==f) continue;
            if (a[s].x+1<=top) {
                ++t; a[t].x=a[s].x+1;
                a[t].now=v[i]; a[t].f=now; a[t].y=a[s].y+len[i]-v1;
            }
        }
    }
    cnt=t;
}
bool calc(int x,double t)
{
    v1=t;  top=min(size[x],U); sz=0;
    for (int i=1;i<=top;i++) mx[i]=-1e18;
    for (int i=point[x];i;i=nxt[i]) {
        if (vis[v[i]]==1) continue;
        st[++sz].now=v[i]; st[sz].x=size[v[i]];
        st[sz].y=len[i];
    }
    sort(st+1,st+sz+1,cmp);
    for (int i=1;i<=sz;i++) {
        cnt=0; 
        if (vis[st[i].now]==1) continue;
        //cout<<st[i].now<<" ";
        get_deep(st[i].now,x,1,st[i].y-v1);
        //for (int j=cnt;j>=1;j--) cout<<a[j].now<<" "<<a[j].x<<" "<<a[j].y<<endl;
        head=tail=1; q[1]=top; p[1]=mx[top];
        for (int j=1;j<=cnt;j++) {
            for (int k=q[tail]-1;k>=L-a[j].x;k--){
                while (mx[k]>=p[tail]&&head<=tail) tail--;
                ++tail; q[tail]=k; p[tail]=mx[k];
            }
            while (q[head]>U-a[j].x&&head<=tail) head++;
            if (head<=tail&&p[head]+a[j].y+eps>=0) return true;
        }
        for (int j=1;j<=cnt;j++) mx[a[j].x]=max(mx[a[j].x],a[j].y);
    }
    //cout<<endl;
    return false;
}
void dfs(int x)
{
    double l=ans; double r=K; 
    while (r-l>eps) {
        double mid=(l*9.0+r)/10.0;
        if (calc(x,mid)) l=mid;
        else r=mid;
    } ans=l; vis[x]=1;
    for (int i=point[x];i;i=nxt[i]) {
        if (vis[v[i]]==1) continue;
        if (size[v[i]]<L) continue;
        root=0; tot=size[v[i]];
        getroot(v[i],x);
        dfs(root);
    }
}
int main()
{
    freopen("rebuild.in","r",stdin);
    freopen("rebuild.out","w",stdout);
    scanf("%d",&n); scanf("%d%d",&L,&U);
    K=0;
    for (int i=1;i<n;i++) {
        int x,y; double z;
        x=get(); y=get(); z=(double)get();
        add(x,y,z);K=max(K,z);
    }
    f[0]=1000000000; root=0; tot=n; 
    getroot(1,0); ans=0;
    dfs(root);
    printf("%.3lf\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值