【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 }