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

此题niubi!

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

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

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

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

数据范围:T,S105n5000

 

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

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

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

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

Sk+1>ans

我们进行移项,有:

Sk×ans>ans

这个式子为啥会优秀很多呢?因为Sk×ans可以在O(n)的时间内求出了:

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

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

 

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

S=vson[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(nlog(ε))确定当前最大的ans

 

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

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

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

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

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

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

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

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

总的复杂度就是O(n2+nlognlog(ε))的。

完结撒花

 

 

复制代码
 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 @   AlphaInf  阅读(261)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示