【WC2015模拟2.6】Tree

10 篇文章 0 订阅
3 篇文章 0 订阅

Description

给出一个n个节点的无根树,每个点有点权。
你要选择一些不相交的路径,如果选择了k条路径,点权和为sum,那么它的价值为 sumk+1
你必须在选择前选择一个数C(0<=c<=T),将所有点权加上C再对limit取模。
求你能收获的最大价值。
N<=10^5,T< limit<=10^6

Solution

昨天才讲完这种题的思路,今天就出了一道题2333
首先可以二分答案啊,然后对于一个mid,判断它是否合法。
就是看 sumk+1>=mid
sum>=mid(k+1)
sumkmid>=mid
那么我们只需要将每一条选择的路径的权值减去mid就好了。
这个东西显然可以Dp。
设F[i][0\1]表示i这个点为根的子树中的权值最大值,和一定能往上继续扩展的权值最大值。
这个东西可以通过前缀和做到O(N)
然后考虑枚举这个C
发现如果我们枚举的这个C,使得点权的最大值不是limit-1,那么C+1显然会比C更优。
于是我们枚举每个点权,将其加到limit-1作为一个C值。
这样做的复杂度就降到O(N^2E)了,其中E是二分的复杂度。
不过这样子做的复杂度还是太高,毕竟二分要写实数。
然后题解神奇的随机化了一发~
随机枚举这N个取值,并且在二分之前判断这个取值是否合法
然后复杂度就降下来了?!
不会证明,我是卡常卡过的(雾

Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define max(a,b) (a>b)?a:b
using namespace std;
typedef double db;
const int N=5*1e3+5,M=1e6+5,inf=0x7fffffff;
int n,l,x,y,tot,limit,T,mx,v[N],V[N],w[N],d[N],q[N];
int last[N],next[N*2],t[N*2];
db ans,f[N][2],sum[N],pre[N],eps=1e-7;
bool h[M];
int get() {
    char ch;while (!isdigit(ch=getchar()));
    int o=ch-48;while (isdigit(ch=getchar())) o=o*10+ch-48;
    return o;
}
void add(int x,int y) {
    t[++l]=y;next[l]=last[x];last[x]=l;
}
void dfs(int x,int y,db z) {
    rep(i,x) if (t[i]!=y) dfs(t[i],x,z);
    q[0]=sum[0]=0;pre[0]=-inf;
    rep(i,x) if (t[i]!=y) q[++q[0]]=t[i];
    fo(i,1,q[0]) sum[i]=sum[i-1]+f[q[i]][1];
    fo(i,1,q[0]) pre[i]=max(pre[i-1]+f[q[i]][1],sum[i-1]+f[q[i]][0]);
    f[x][1]=sum[q[0]];f[x][0]=sum[q[0]]+v[x]-z;
    fo(i,1,q[0]) f[x][0]=max(f[x][0],f[q[i]][0]+sum[q[0]]-f[q[i]][1]+v[x]);
    fo(i,1,q[0]) f[x][1]=max(f[x][1],f[q[i]][0]+pre[i-1]+v[x]+z+sum[q[0]]-sum[i]);
    f[x][1]=max(f[x][1],f[x][0]);
}
void solve(int x) {
    db l=ans,r=5*1e8;
    fo(i,1,n) v[i]=(V[i]+x)%limit;
    dfs(1,0,ans+eps);
    if (max(f[1][0],f[1][1])<ans+eps) return;
    while (r-l>eps) {
        db mid=(l+r)/2.0;
        dfs(1,0,mid);
        if (max(f[1][0],f[1][1])>=mid) l=mid;
        else r=mid; 
    }
    ans=l;
}
int main() {
    n=get();limit=get();
    fo(i,1,n) V[i]=get(),V[i]%=limit,mx=max(mx,V[i]);
    fo(i,1,n-1) x=get(),y=get(),add(x,y),add(y,x);
    T=get();
    fo(i,1,n) if (limit-V[i]-1<=T&&!h[limit-V[i]-1]) solve(limit-V[i]-1),h[limit-V[i]-1]=1;
    if (mx+T<limit-1) solve(T);
    printf("%.6lf\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值