CF1120D Power Tree——一题多解
https://www.luogu.com.cn/problem/CF1120D
给你一棵树,想象你可以对于每个点 ,用 的花费得到子树中所有叶子的权值和,你想要解出所有叶子的权值,最少要多少花费?(相较于原题,题意有改动,是根据模拟赛的题意来的)
法1.区间转差分的思想+最小生成树
你可以把叶子按从左到右(直观的)看成一个序列,每个点可以对序列的一个可知的区间进行区间查和这样的(想象),运用区间转差分的思想, 可以换成 ,在一个大小为 的图里将 连一条权值为 的边;只需要最后叶子们都连通,根据 元一次方程组需要 个方程解出,则转化为最小生成树问题。
难点在于第二问,对于最小生成树的边数组(已排序)连续的一段一样的边权的边,合法的都可能,而处理完这一段得到的图的连通性都是一样的,所以不会影响后续。具体不太好说,看代码吧。
复制#include <bits/stdc++.h> using namespace std; const int N=1e6+5; int n,dfc,id_lf,c[N],u[N],v[N],fa[N],lf[N],rf[N],dfn[N],num[N]; vector<int>G[N]; struct J{int u,v,w,id;}e[N]; int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);} void unite(int x,int y){fa[find(y)]=find(x);} void dfs1(int x,int p){ bool is_lf=1; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p)is_lf=0,dfs1(y,x); } if(is_lf)num[x]=++id_lf; } void dfsa(int x,int p){ dfn[++dfc]=x; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p)dfsa(y,x); } rf[x]=num[dfn[dfc]]; } void dfsb(int x,int p){ dfn[++dfc]=x; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p)dfsb(y,x); } lf[x]=num[dfn[dfc]]; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&c[i]); for(int i=1;i<n;i++)scanf("%d%d",&u[i],&v[i]); for(int i=1;i<n;i++)G[u[i]].push_back(v[i]),G[v[i]].push_back(u[i]); dfs1(1,0);for(int i=1;i<=id_lf+1;i++)fa[i]=i; dfsa(1,0); for(int i=1;i<=n;i++)G[i].clear(); for(int i=n-1;i;i--)G[u[i]].push_back(v[i]),G[v[i]].push_back(u[i]); dfc=0,dfsb(1,0); for(int i=1;i<=n;i++)/*cout<<lf[i]<<' '<<rf[i]<<'\n',*/e[i].u=lf[i],e[i].v=rf[i]+1,e[i].w=c[i],e[i].id=i; sort(e+1,e+n+1,[](J a,J b){return a.w<b.w;}); vector<int>st; long long sum=0; for(int i=1;i<=n;i++){ int j=i-1; while(j<n&&e[j+1].w==e[i].w){ j++; if(find(e[j].u)!=find(e[j].v))st.push_back(e[j].id); } j=i-1; while(j<n&&e[j+1].w==e[i].w){ j++; if(find(e[j].u)!=find(e[j].v))unite(e[j].u,e[j].v),sum+=e[j].w; } i=j; } cout<<sum<<' '<<st.size()<<'\n'; sort(st.begin(),st.end()); for(int i=0;i<st.size();i++)cout<<st[i]<<' '; }
法2.DP
也可以用树形dp做,但我还没有调出来,因为我的写法很折腾人。这个做法远远不如法1来得优美,但没办法,你看下面这个plus问题
第三问:最优方案有几种?两种方案不同当且仅当取的 的集合不同。
如果沿用最小生成树的方法,将可以以连续相同的一段为子问题形成一些求生成树个数的问题,即矩阵树定理解决,复杂度很高,弃用。而这里使用树dp就会很方便。
设 表示以 为根的子树中的所有叶子只剩 个没有被解出来需要的最小花费, 表示对应方案数。
依次枚举子节点并递归,那么每个时刻最开始 都代表前...个子节点的子树中的所有叶子...的对应值:
f[x][0]=f[x][1]=0,g[x][0]=g[x][1]=1; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p){ dfs(y,x); if(f[x][1]+f[y][0]<f[x][0]+f[y][1]) f[x][1]+=f[y][0],g[x][1]=1ll*g[x][1]*g[y][0]%mod; else { if(f[x][1]+f[y][0]==f[x][0]+f[y][1]) g[x][1]=(1ll*g[x][1]*g[y][0]%mod+1ll*g[x][0]*g[y][1]%mod)%mod; else g[x][1]=1ll*g[x][0]*g[y][1]%mod; f[x][1]=f[x][0]+f[y][1];//注意处理f/g的先后关系 } f[x][0]+=f[y][0],g[x][0]=1ll*g[x][0]*g[y][0]%mod;//注意处理0/1的先后关系 } }
mycode
#include <bits/stdc++.h>//Power Tree+: 三个问 using namespace std; const int N=1e6+5,mod=998244353; int n,dfc,id_lf,c[N],u[N],v[N],fa[N],lf[N],rf[N],dfn[N],num[N],g[N][2]; long long f[N][2]; vector<int>G[N]; struct J{int u,v,w,id;}e[N]; int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);} void unite(int x,int y){fa[find(y)]=find(x);} void dfs1(int x,int p){ bool is_lf=1; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p)is_lf=0,dfs1(y,x); } if(is_lf)num[x]=++id_lf; } void dfsa(int x,int p){ dfn[++dfc]=x; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p)dfsa(y,x); } rf[x]=num[dfn[dfc]]; } void dfsb(int x,int p){ dfn[++dfc]=x; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p)dfsb(y,x); } lf[x]=num[dfn[dfc]]; } void dfs(int x,int p){ f[x][0]=f[x][1]=0,g[x][0]=g[x][1]=1; bool is_lf=1; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(y^p){ is_lf=0; dfs(y,x); if(f[x][1]+f[y][0]<f[x][0]+f[y][1]) f[x][1]+=f[y][0],g[x][1]=1ll*g[x][1]*g[y][0]%mod; else { if(f[x][1]+f[y][0]==f[x][0]+f[y][1]) g[x][1]=(1ll*g[x][1]*g[y][0]%mod+1ll*g[x][0]*g[y][1]%mod)%mod; else g[x][1]=1ll*g[x][0]*g[y][1]%mod; f[x][1]=f[x][0]+f[y][1]; } f[x][0]+=f[y][0],g[x][0]=1ll*g[x][0]*g[y][0]%mod; } } if(is_lf){f[x][0]=c[x];return;} if(f[x][0]>f[x][1]+c[x])f[x][0]=f[x][1]+c[x],g[x][0]=g[x][1]; else if(f[x][0]==f[x][1]+c[x])g[x][0]=(g[x][0]+g[x][1])%mod; } void print(int x){ if(x/10)print(x/10); putchar(x%10+48); } int main(){ freopen("purtree.in","r",stdin);freopen("purtree.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&c[i]); for(int i=1;i<n;i++)scanf("%d%d",&u[i],&v[i]); for(int i=1;i<n;i++)G[u[i]].push_back(v[i]),G[v[i]].push_back(u[i]); dfs1(1,0);for(int i=1;i<=id_lf+1;i++)fa[i]=i; dfsa(1,0); for(int i=1;i<=n;i++)G[i].clear(); for(int i=n-1;i;i--)G[u[i]].push_back(v[i]),G[v[i]].push_back(u[i]); dfc=0,dfsb(1,0); for(int i=1;i<=n;i++)/*cout<<lf[i]<<' '<<rf[i]<<'\n',*/e[i].u=lf[i],e[i].v=rf[i]+1,e[i].w=c[i],e[i].id=i; sort(e+1,e+n+1,[](J a,J b){return a.w<b.w;}); vector<int>st; long long sum=0; for(int i=1;i<=n;i++){ int j=i-1; while(j<n&&e[j+1].w==e[i].w){ j++; if(find(e[j].u)!=find(e[j].v))st.push_back(e[j].id); } j=i-1; while(j<n&&e[j+1].w==e[i].w){ j++; if(find(e[j].u)!=find(e[j].v))unite(e[j].u,e[j].v),sum+=e[j].w; } i=j; } cout<<sum<<'\n'; int cs;cin>>cs; sort(st.begin(),st.end()); if(cs>=2)for(int i=0;i<st.size();i++)print(st[i]),putchar(' ');puts(""); if(cs>=3)dfs(1,0),print(g[1][0]); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App