CF1120D Power Tree——一题多解

https://www.luogu.com.cn/problem/CF1120D

给你一棵树,想象你可以对于每个点 x,用 cx 的花费得到子树中所有叶子的权值和,你想要解出所有叶子的权值,最少要多少花费?(相较于原题,题意有改动,是根据模拟赛的题意来的)n106,1cx109

法1.区间转差分的思想+最小生成树

你可以把叶子按从左到右(直观的)看成一个序列,每个点可以对序列的一个可知的区间进行区间查和这样的(想象),运用区间转差分的思想,[l,r] 可以换成 l,r+1,在一个大小为 cnt_leaf+1 的图里将 l,r+1 连一条权值为 cx 的边;只需要最后叶子们都连通,根据 n 元一次方程组需要 n 个方程解出,则转化为最小生成树问题。
难点在于第二问,对于最小生成树的边数组(已排序)连续的一段一样的边权的边,合法的都可能,而处理完这一段得到的图的连通性都是一样的,所以不会影响后续。具体不太好说,看代码吧。

复制
#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问题

第三问:最优方案有几种?两种方案不同当且仅当取的 x 的集合不同。

如果沿用最小生成树的方法,将可以以连续相同的一段为子问题形成一些求生成树个数的问题,即矩阵树定理解决,复杂度很高,弃用。而这里使用树dp就会很方便。

f[i][0/1] 表示以 i 为根的子树中的所有叶子只剩 0/1 个没有被解出来需要的最小花费,g[i][0/1] 表示对应方案数。
依次枚举子节点并递归,那么每个时刻最开始 f,g 都代表前...个子节点的子树中的所有叶子...的对应值:

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]);
}
posted @   pengyule  阅读(53)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示