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
#include
#include
#include
#include
#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.xx;
}
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]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 (ss;
        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<" ";
        get_deep(st[i].now,x,1,st[i].y-v1);
        //for (int j=cnt;j>=1;j--) cout<" "<x<<" "<y<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<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]]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;iint 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);
}

你可能感兴趣的:(点分治,01分数规划)