【2019北京集训六】路径(path) 二分+DP

此题niubi!

题目大意:给你一颗n个点的点带权无根树,现在请您进行以下两步操作:

1,选择一个$[0,T]$之间的整数$C$,并令所有的点权$wi$变为$(wi+C)%MOD$

2,选择若干条点不相交的路径;设选择的条数为$k$,覆盖的点的点权和为$S$,则收益为$\frac{S}{k+1}$

请您求出收益最大可能为多少。

数据范围:$T,S≤10^5$,$n≤5000$

 

我们先不考虑修改点权的过程,只考虑求最大收益应该如何做。

我们显然有一种$O(n^2)$的做法,但是复杂度太高了,加上修改点权的操作就爆炸了,考虑优化。

我们考虑二分这个收益,设当前二分到的收益是$ans$。

假设真实收益大于$ans$,则有:

$\frac{S}{k+1}>ans$

我们进行移项,有:

$S-k\times ans>ans$

这个式子为啥会优秀很多呢?因为$S-k\times ans$可以在$O(n)$的时间内求出了:

我们设$f[u]$表示以$u$为跟的子树内找出了$x$条路径,($x$条路径的和$-x\times ans$)的最大值,且没有路径连出$u$

$g[u]$表示以$u$为跟的子树内找出了$x$条路径,($x$条路径的和$-(x-1)\times ans$)的最大值,有恰好一条路径从$u$连出

 

我们在以$u$为跟的子树中找出两个儿子$v1$,$v2$,满足$v1$在所有儿子中,$g[v1]-f[v1]$最大,$v2$次大。

设$S=\sum \limits_{v∈son[u]} f[u]$,则有:

$f[u]=max(S,S+(g[v1]-f[v1])+(g[v2]-f[v2]+val[u])-ans)$

$g[u]=max(S,S+(g[v1]-f[v1])+val[u])$

其中$val[u]$表示点$u$的权值。

我们通过二分$ans$,就可以在$O(n\log(-\varepsilon))$确定当前最大的$ans$。

 

我们考虑点权的修改,一个有效的点权修改方案,需满足至少一个$val[i]$被修改至$MOD-1$,或者增加的值为$T$。

如果每次暴力修改,然后二分查找$ans$,这个复杂度显然是$O(n\log(-\varepsilon))$的,依然会爆炸。

设$Ans[i]$表示当点权被增加了$c[i]$时的$ans$

我们考虑到,一个长度为$n$的随机序列的最长上升子序列期望长度是$O(\log\  n)$的

我们考虑将序列$C$随机打乱

我们先将序列进行修改,然后基于之前求出的$ans$,求一遍答案,判断修改后的序列是否会更加优秀。

如果更加优秀,我们就重新二分出答案。

不难发现,重新二分答案的次数期望是$O(\log\ n)$的。

总的复杂度就是$O(n^2+n\log n\log(-\varepsilon))$的。

完结撒花

 

 

 1 #include<bits/stdc++.h>
 2 #define M 5005
 3 #define eps 1e-6
 4 using namespace std;
 5 
 6 struct edge{int u,next;}e[M*2]={0}; int head[M]={0},use=0;
 7 void add(int x,int y){use++;e[use].u=y;e[use].next=head[x];head[x]=use;}
 8 
 9 double f[M]={0},g[M]={0},Ans=0;
10 double get(int x){return g[x]-f[x];}
11 
12 int n,MOD,T,val[M]={0},Val[M]={0};
13 
14 void dfs(int x,int fa){
15     int max1=0,max2=0;
16     double sumf=0;
17     for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){
18         int v=e[i].u;
19         dfs(v,x);
20         if(get(v)>get(max1)) max2=max1,max1=v;
21         else if(get(v)>get(max2)) max2=v;
22         sumf+=f[v];
23     }
24     f[x]=max(sumf,sumf+get(max1)+get(max2)+val[x]-Ans);
25     g[x]=max(max(sumf,sumf+val[x]),sumf+get(max1)+val[x]);
26 }
27 
28 int cnt=0,c[M]={0};
29 
30 bool check(double chg){
31     Ans=chg;
32     dfs(1,0);
33     return f[1]>=chg-eps;
34 }
35 
36 int main(){
37     scanf("%d%d",&n,&MOD);
38     for(int i=1;i<=n;i++) scanf("%d",Val+i),Val[i]%=MOD;
39     for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
40     scanf("%d",&T);
41     c[++cnt]=0; c[++cnt]=T;
42     for(int i=1;i<=n;i++){
43         c[++cnt]=MOD-1-Val[i];
44         if(c[cnt]>T){c[cnt--]=0; continue;}
45     }
46     memcpy(val,Val,sizeof(val));
47 
48     random_shuffle(c+1,c+cnt+1);
49     double ans=0;
50     for(int hh=1;hh<=cnt;hh++){
51         for(int i=1;i<=n;i++) val[i]=(Val[i]+c[hh])%MOD;
52         if(!check(ans)) continue;
53         double l=0,r=n*MOD/2;
54         while(r-l>eps){
55             double mid=(l+r)/2;
56             if(check(mid)) l=mid;
57             else r=mid;
58         }
59         ans=max(ans,l);
60     }
61     printf("%.10lf\n",ans);
62 }

 

posted @ 2019-04-09 15:31  AlphaInf  阅读(260)  评论(0编辑  收藏  举报