【HNOI/AHOI2018】道路

题目链接

分析:

  这实际上就是给了一个二叉树,每个非叶子节点选一个儿子,然后对于每个叶子结点,它的贡献跟它的到根链上有几个节点作为左/右儿子没被选中有关,求最小的贡献和。

  考虑树形$DP$,然而这个贡献是个乘积,不方便直接拆分然后从下往上做套路的子树合并。

  观察数据范围:树的大小$4\times 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;

}
View Code

小结:

  根据数据范围灵活选用方便的算法。

  写题的时候设节点花了点时间,有点手生了。

猜你喜欢

转载自www.cnblogs.com/Hansue/p/12104308.html