分析:
这实际上就是给了一个二叉树,每个非叶子节点选一个儿子,然后对于每个叶子结点,它的贡献跟它的到根链上有几个节点作为左/右儿子没被选中有关,求最小的贡献和。
考虑树形$DP$,然而这个贡献是个乘积,不方便直接拆分然后从下往上做套路的子树合并。
观察数据范围:树的大小$4 imes 10^4$,深度$40$。发现对于每个叶子,可以枚举顶上的信息然后存下来;非叶子结点也是枚举顶上信息,再讨论两种情况。
设$f[u][i][j]$表示:对于子树$u$,它的到根链上有$i$个没被选中的左二子、$j$个被选中的右儿子,子树的贡献和。那么$$f[u][i][j]=min{f[ls][i][j]+f[rs][i][j+1],f[ls][i+1][j]+f[rs][i][j]}$$
题解和讨论区里好像说这题卡空间,那么我们可以根据$DFS$的性质,设$f[d][i][j][0/1]$表示第$d$层,左/右儿子的答案。顺利通过。
实现(100分):

#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> #include<map> #include<set> #define IL inline #define RG register #define _1 first #define _2 second using namespace std; typedef long long LL; const int N=2e4; const int M=40; int n,cit[N+3],vil[N+3]; struct Node{ bool iscity; LL a,b,c; int s[2]; }a[N*2+3]; int top; IL int pickcity(int i){ if(cit[i]==-1) return cit[i]=++top; return cit[i]; } LL f[M+3][M+3][M+3][2]; void dfs(int d,int t,int x,int rd,int ri){ if(a[x].iscity){ for(int i=0;i<2;i++) dfs(d+1,i,a[x].s[i],rd+(int)(i==0),ri+(int)(i==1)); for(int i=0;i<=rd;i++) for(int j=0;j<=ri;j++) f[d][i][j][t]=min(f[d+1][i][j][0]+f[d+1][i][j+1][1] ,f[d+1][i+1][j][0]+f[d+1][i][j][1]); } else { for(int i=0;i<=rd;i++) for(int j=0;j<=ri;j++) f[d][i][j][t]=a[x].c*(a[x].a+i)*(a[x].b+j); } } int main(){ scanf("%d",&n); top=-1; memset(cit,-1,sizeof cit); for(int i=1;i<n;i++){ int k=pickcity(i); a[k].iscity=true; a[k].s[0]=a[k].s[1]=-1; int s[2],t; scanf("%d%d",&s[0],&s[1]); for(int j=0;j<2;j++) if(s[j]>0){ t=pickcity(s[j]); a[t].iscity=true; a[k].s[j]=t; } else { t=++top; a[t].iscity=false; vil[-s[j]]=t; a[k].s[j]=t; } } for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[vil[i]].a,&a[vil[i]].b,&a[vil[i]].c); dfs(1,0,0,0,0); printf("%lld",f[1][0][0][0]); return 0; }
小结:
根据数据范围灵活选用方便的算法。
写题的时候设节点花了点时间,有点手生了。