题目大意
有一颗点权树,选择k条点不相交的树路径,价值为覆盖点的点权和除以k+1。
现在你可以给每个点的点权由x变成(x+c)%lim。0<=c<=m。
保证
m<lim,x<lim
。
求最大价值。
分数规划
先考虑不改变点权或是说点权已知的做法。
怎么做都发现如果要求用几条树路径的最大价值,都无法很快的做。
因此正解肯定不用求用多少每条树路径的最大价值。
考虑分数规划。二分答案ans,去判定
Sk+1>=ans
S−ans∗k−ans>=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);
}