【xsy1130】tree 树形dp+期望dp
题目写得不清不楚的。。。
题目大意:给你一棵n个节点的树,你会随机选择其中一个点作为根,随后随机每个点深度遍历其孩子的顺序。
下面给你一个点集S,问你遍历完S中所有点的期望时间,点集S中的点可能会重复。
数据范围:n≤105
我们考虑钦定根,然后暴力dp。
设s[u]表示遍历以u为根的子树的耗时。
设f[u]表示开始遍历子树u,且最后遍历在子树u中结束的期望耗时。
不难发现,s[u]=2×siz[u]−2,其中siz[u]为以u为根的子树的节点个数。
对于u的孩子,我们把它们分成黑点和白点两类,其中黑点v代表以v为根的子树内包含有集合S中的点,白点代表不包含有集合S中的点。
对于任意一种遍历顺序而言,遍历特征如图所示:
显然,bm后的节点是不需要遍历的。
设我们总共有m个黑点,则有:
f[u]=m−1m∑col[v]=blacks[v]+1m∑col[v]=black(f[v]+1)+mm+1∑col[v]=whites[v]
此处的v必须满足是u的儿子。
我们通过这个O(n2)的暴力转移就可以获得70分的好成绩。
考虑满分做法,我们以1为根执行一次dfs,求出所有点的f值和s值。
我们进行第二次dfs,在dfs的过程中维护u的父亲的F值。
然后套入刚刚的公式中去求即可。
复杂度就降低到了O(n)

1 #include<bits/stdc++.h> 2 #define M 1005 3 using namespace std; 4 5 struct edge{int u,next;}e[M*2]={0}; int head[M]={0},use=0; 6 void add(int x,int y){use++;e[use].u=y;e[use].next=head[x];head[x]=use;} 7 8 int siz[M]={0},n,S,is[M]={0},ok[M]={0}; 9 double s[M]={0},f[M]={0}; 10 11 void dfs(int x,int fa){ 12 siz[x]=1; ok[x]=is[x]; 13 int m=0; 14 double sumb=0,sumf=0,sumw=0; 15 for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){ 16 dfs(e[i].u,x); 17 siz[x]+=siz[e[i].u]; 18 ok[x]+=ok[e[i].u]; 19 if(ok[e[i].u]){ 20 m++; 21 sumb+=s[e[i].u]; 22 sumf+=f[e[i].u]+1; 23 }else{ 24 sumw+=s[e[i].u]; 25 } 26 } 27 s[x]=2*siz[x]; 28 if(m){ 29 f[x]=sumb*(m-1)/m+sumf/m+sumw*m/(m+1); 30 } 31 } 32 33 int main(){ 34 scanf("%d",&n); 35 for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x); 36 scanf("%d",&S); 37 for(int i=1,x;i<=S;i++) scanf("%d",&x),is[x]=1; 38 double ans=0; 39 for(int i=1;i<=n;i++){ 40 memset(ok,0,sizeof(ok)); 41 memset(siz,0,sizeof(siz)); 42 memset(s,0,sizeof(s)); 43 memset(f,0,sizeof(f)); 44 dfs(i,0); 45 ans+=f[i]; 46 } 47 printf("%.10lf\n",ans/n); 48 }
这是正解:
1 #include<bits/stdc++.h> 2 #define M 100005 3 #define D double 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 int siz[M]={0},n,S,is[M]={0},ok[M]={0}; 10 D s[M]={0},f[M]={0},ans=0; 11 12 void dfs(int x,int fa){ 13 siz[x]=1; ok[x]=is[x]; 14 int m=0; 15 D sumb=0,sumf=0,sums=0; 16 for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){ 17 dfs(e[i].u,x); 18 siz[x]+=siz[e[i].u]; 19 ok[x]+=ok[e[i].u]; 20 if(ok[e[i].u]){ 21 m++; 22 sumb+=s[e[i].u]; 23 sumf+=f[e[i].u]+1; 24 }else{ 25 sums+=s[e[i].u]; 26 } 27 } 28 s[x]=2*siz[x]; 29 if(m){ 30 f[x]=sumb*(m-1)/m+sumf/m+sums*m/(m+1); 31 } 32 } 33 void dfs(int x,int fa,D F){ 34 int OK=S-ok[x],m=bool(OK); 35 D sumb=0,sumf=0,sums=0; 36 if(m) sumf+=F,sumb+=2*(n-siz[x]); else sums+=2*(n-siz[x]); 37 for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){ 38 if(ok[e[i].u]) m++,sumb+=s[e[i].u],sumf+=f[e[i].u]+1; 39 else sums+=s[e[i].u]; 40 } 41 D res=0; if(m) res=sumb*(m-1)/m+sumf/m+sums*m/(m+1);ans+=res; 42 for(int i=head[x];i;i=e[i].next) if(e[i].u!=fa){ 43 if(ok[e[i].u]){ 44 m--; sumb-=s[e[i].u]; sumf-=f[e[i].u]+1; 45 if(m) F=sumb*(m-1)/m+sumf/m+sums*m/(m+1); else F=0; 46 m++; sumb+=s[e[i].u]; sumf+=f[e[i].u]+1; 47 }else{ 48 sums-=s[e[i].u]; 49 if(m) F=sumb*(m-1)/m+sumf/m+sums*m/(m+1); else F=0; 50 sums+=s[e[i].u]; 51 } 52 dfs(e[i].u,x,F+1); 53 } 54 } 55 56 int main(){ 57 scanf("%d",&n); 58 for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x); 59 scanf("%d",&S); int SS=0; 60 for(int i=1,x;i<=S;i++) scanf("%d",&x),SS+=(is[x]==0),is[x]=1; 61 dfs(1,0); S=SS; 62 dfs(1,0,0); 63 printf("%.10lf\n",ans/n); 64 }
【推荐】国内首个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帮你做增删改查!!