Tree

题目大意

有一颗点权树,选择k条点不相交的树路径,价值为覆盖点的点权和除以k+1。
现在你可以给每个点的点权由x变成(x+c)%lim。0<=c<=m。
保证 m<lim,x<lim
求最大价值。

分数规划

先考虑不改变点权或是说点权已知的做法。
怎么做都发现如果要求用几条树路径的最大价值,都无法很快的做。
因此正解肯定不用求用多少每条树路径的最大价值。
考虑分数规划。二分答案ans,去判定
Sk+1>=ans
Sanskans>=0
S表示点权和,每有一条树路径,就要-ans。考虑最大化左式。
设f[i]表示在i子树中选择树路径,最后点i被覆盖且可以向上延伸,g[i]表示i未被覆盖或不可向上延伸。
f[i]有两种转移,一种是i作为新的树路径起点,此时树路径条数+1。一种是被子树中一条向上延伸的树路径覆盖,并继续向上延伸。
g[i]有两种转移,一种是i不被覆盖。一种是被子树中两条向上延伸的树路径都覆盖,这两条树路径合并为了一条树路径,不可再延伸,此时树路径条数-1。
枚举c的话复杂度是nm log e,log e表示二分复杂度。

改变权值

可以改变权值到底怎么办?
考虑当前+c的方案,如果不存在一个点的点权+1模lim后为0,显然+c+1会更优。
这样的话,关键取值只有至多n个,枚举这些关键取值即可。
复杂度n^2 log e.

继续优化

首先知道一个结论:一个全随机序列,从第一个位置开始往右,每次找到右边第一个大于当前位置的位置跳过去,只会跳期望log n步。
证明:考虑从1开始往右跳,一个位置能被跳到就会贡献1,于是根据期望的线性性,只需要考虑每个位置的贡献,如果i能贡献,即i会被跳到,那么i一定是[1,i]的最大值,概率为1/i。相加是个调和级数,即log n,因此得证。。
这启发了我们什么呢?
每种取值可以定义一种函数h,表示该取值下算出的最优答案。
如果h是全随机的,答案的更新次数只有log n次。
可惜现在h不是全随机的,但是我们可以通过随机打乱取值序列来使得h可以等价于全随机序列。
现在关键在于如何快速忽略答案不优于当前答案的取值。
虽然很难快速算一个取值的最优答案,但是检验一个答案是否优于一个取值的最优答案是很容易的,直接套用二分时的check即可,实在不会看代码。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef double db;
const int maxn=5000+10,inf=500000000;
const db Inf=10000000000000;
const db eps=1e-7;
db f[maxn],g[maxn],fg[maxn];
int a[maxn],w[maxn],b[maxn],h[maxn],go[maxn*2],next[maxn*2];
int i,j,k,t,n,m,tot,top,lim,sum;
db l,r,mid,ans;
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void dfs(int x,int y,db ans){
    int t=h[x];
    f[x]=g[x]=-Inf;
    while (t){
        if (go[t]!=y) dfs(go[t],x,ans);
        t=next[t];
    }
    int j=0,k=0;
    db l=0,r;
    t=h[x];
    while (t){
        if (go[t]!=y){
            if (!j||f[go[t]]-fg[go[t]]>f[j]-fg[j]){
                k=j;
                j=go[t];
            }
            else if (!k||f[go[t]]-fg[go[t]]>f[k]-fg[k]) k=go[t];
            l+=fg[go[t]];
        }
        t=next[t];
    }
    if (j) f[x]=max(f[x],l+f[j]-fg[j]+w[x]);
    f[x]=max(f[x],l+w[x]-ans);
    if (j&&k) g[x]=max(g[x],l+f[j]-fg[j]+f[k]-fg[k]+w[x]+ans);
    g[x]=max(g[x],l);
    fg[x]=max(f[x],g[x]);
}
bool check(db ans){
    int i,j;
    /*fo(i,1,top){
        fo(j,1,n) w[j]=(a[j]+b[i])%lim;*/
        dfs(1,0,ans);
        db t=fg[1];
        if (t-ans>=0/*||fabs(t-ans)<eps*/) return 1;
    //}
    return 0;
}
int main(){
    //freopen("data.in","r",stdin);//freopen("data.out","w",stdout);
    scanf("%d%d",&n,&lim);
    fo(i,1,n) scanf("%d",&a[i]),sum+=a[i];
    fo(i,1,n-1){
        scanf("%d%d",&j,&k);
        add(j,k);add(k,j);
    }
    scanf("%d",&m);
    fo(i,1,n) b[++top]=lim-a[i]-1;
    b[++top]=m;
    sort(b+1,b+top+1);
    top=unique(b+1,b+top+1)-b-1;
    while (top&&b[top]>m) top--;
    random_shuffle(b+1,b+top+1);
    fo(i,1,top){
        fo(j,1,n) w[j]=(a[j]+b[i])%lim;
        if (!check(ans)) continue;
        l=ans;r=inf;
        while (fabs(r-l)>=eps){
            mid=(l+r+eps)/2;
            if (check(mid)) l=mid;else r=mid-eps;
        }
        if (check(l)&&l>ans) ans=l;
        //printf("%d %.6lf\n",i,ans);
    }
    /*l=0;r=inf;
    while (r-l>=eps){
        mid=(l+r+eps)/2;
        if (check(mid)) l=mid;else r=mid-eps;
    }*/
    printf("%.6lf\n",l);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值