【2019北京集训六】路径(path) 二分+DP
此题niubi!
题目大意:给你一颗n个点的点带权无根树,现在请您进行以下两步操作:
1,选择一个[0,T]之间的整数C,并令所有的点权wi变为(wi+C)
2,选择若干条点不相交的路径;设选择的条数为k,覆盖的点的点权和为S,则收益为Sk+1
请您求出收益最大可能为多少。
数据范围:T,S≤105,n≤5000
我们先不考虑修改点权的过程,只考虑求最大收益应该如何做。
我们显然有一种O(n2)的做法,但是复杂度太高了,加上修改点权的操作就爆炸了,考虑优化。
我们考虑二分这个收益,设当前二分到的收益是ans。
假设真实收益大于ans,则有:
Sk+1>ans
我们进行移项,有:
S−k×ans>ans
这个式子为啥会优秀很多呢?因为S−k×ans可以在O(n)的时间内求出了:
我们设f[u]表示以u为跟的子树内找出了x条路径,(x条路径的和−x×ans)的最大值,且没有路径连出u
g[u]表示以u为跟的子树内找出了x条路径,(x条路径的和−(x−1)×ans)的最大值,有恰好一条路径从u连出
我们在以u为跟的子树中找出两个儿子v1,v2,满足v1在所有儿子中,g[v1]−f[v1]最大,v2次大。
设S=∑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(nlog(−ε))确定当前最大的ans。
我们考虑点权的修改,一个有效的点权修改方案,需满足至少一个val[i]被修改至MOD−1,或者增加的值为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 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!