题目大意
给定一棵有n个节点的点带权(
另外,在选取路径之前,你必须执行一次如下的操作(操作分为三个步骤):
- 选定整数参数C,满足
C∈[0,T] - 将所有点权加上C
- 将所有点权对
LIMIT 取模。
其中T,
1≤n≤5×103,vi,t<LIMIT≤105
题目分析
先不考虑必须要执行的操作,假设我知道每一个点的权值,怎么做呢?
分数规划的常见套路:考虑二分答案ans,那么当前答案可行当且仅当存在方案满足:
Sk+1>ans
即
S−k×ans>ans]
那么怎么判断当前答案是否可行呢?可以发现,如果我们在计算时将每一条路径各自带来的得分都减去ans,那么左边式子是可以看成与路径条数k无关的。
因此我们使用动态规划算法来判定答案是否可行。我们随便选一个根节点,设
这个瞎讨论一下就可以转移,时间复杂度是O(n)的。也就是说我们可以在线性时间内完成对答案可行性的判定。
那么现在要考虑执行操作,显然我们没有必要对于[0,T]内的每一个数都考察一遍,因为如果有很多个C,它们都不会造成任何一个
这让还不能得到所有分数,怎么办呢?
在这题,我们可以线性判断对于一个点权序列,一个答案是否可行,但是要具体求出对于这个点权序列的最优解,就要算上二分的复杂度。
那么我们考虑加上一个优化,每次二分之前,先判定对于当前要考察的点权序列,目前的最优解是否可行,如果不可行,那么在这个点权序列我们不可能计算出更加优的结果,就不用在这里二分了。
但是这样我依然有可能每次都要二分,毕竟有可能很不好运,每一个考察值的答案都比前一个优。那怎么办?我们随机化!
将所有考察值去重,然后随机打乱,使用上面的方法判断。这样期望只会有logn个值要进行二分。这个证明需要用到一下结论:
- 一个n个数随机顺序的排列,我们从第一个数开始,每次跳到后面第一个比自己大的位置,期望情况下需要
logn 步。 - 证明:根据期望的线性性,我们只要计算出每一个位置对步数造成贡献的概率之和。显然一个位置i对答案造成贡献当且仅当在它前面没有更大的数,这个概率为
1i ,我们累加起来就是调和级数,是O(logn)的。
于是这里的时间复杂度变为O(n2+nElogn)了。
代码实现
请注意代码常数……
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <ctime>
using namespace std;
typedef double db;
const db EPS=1e-8;
const db INF=1e+40;
const int N=5005;
const int E=N<<1;
int c[N],v[N],last[N];
int nxt[E],tov[E];
int n,T,LIM,tot,m;
db f[N],g[N];
db ans,sum;
void insert(int x,int y){tov[++tot]=y,nxt[tot]=last[x],last[x]=tot;}
void dfs(int x,int fa,db cur)
{
f[x]=-cur,g[x]=0.0;
db d1=-INF,d2=-INF,gsum=0.0;
for (int i=last[x],y;i;i=nxt[i])
if ((y=tov[i])!=fa)
{
dfs(y,x,cur);
gsum+=g[y];
if (d1<f[y]-g[y]) d2=d1,d1=f[y]-g[y];
else if (d2<f[y]-g[y]) d2=f[y]-g[y];
}
f[x]=max(f[x],d1)+gsum+v[x];
g[x]=max(g[x],max(d1+d2+cur+v[x],d1+v[x]))+gsum;
}
bool judge(db cur)
{
dfs(1,0,cur);
return max(f[1],g[1])>=cur;
}
db calc(db l,db r)
{
db mid,ret=l;
for (;r-l>=EPS;)
{
mid=(l+r)/2.0;
if (judge(mid)) l=(ret=mid)+EPS;
else r=mid-EPS;
}
return ret;
}
void solve()
{
ans=0.0;
for (int i=1;i<=m;i++)
{
for (int j=1;j<=n;j++) v[j]+=c[i],v[j]>=LIM?v[j]-=LIM:0;
if (judge(ans)) ans=calc(ans,sum);
for (int j=1;j<=n;j++) v[j]-=c[i],v[j]<0?v[j]+=LIM:0;
}
}
int main()
{
srand(time(0));
freopen("tree.in","r",stdin),freopen("tree.out","w",stdout);
scanf("%d%d",&n,&LIM);
for (int i=1;i<=n;i++) scanf("%d",&v[i]),sum+=v[i]+LIM;
for (int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),insert(x,y),insert(y,x);
scanf("%d",&T);
for (int i=1;i<=n;i++) c[i]=min(T,LIM-1-v[i]);
sort(c+1,c+1+n);
m=0;
for (int i=1;i<=n;i++) if (i==1||c[i]!=c[m]) c[++m]=c[i];
random_shuffle(c+1,c+1+m);
solve();
printf("%.6lf\n",ans);
fclose(stdin),fclose(stdout);
return 0;
}