BZOJ4446: [Scoi2015]小凸玩密室(DP)

传送门

题解:
看网上的做法都十分的迷。就来水一篇博客吧。

f[i][j] f [ i ] [ j ] 表示遍历以 i i 为根的子树,最后以j结尾的最小代价。

注意这是 O(nlogn) O ( n log ⁡ n ) 的,因为每个点只有 logn log ⁡ n 个父亲。

处理这个数组也十分Naive,只需要在每个点分别枚举先进入左子树还是右子树,然后最后从哪个节点进入另一个子树即可。这也是 O(nlogn) O ( n log ⁡ n ) 的。

然后处理出 g[i] g [ i ] 表示处理完 i i 的子树并跳到i的爷爷的最小代价。

枚举每个点,用 dp d p 数组来优化模拟的过程,暴力往上跳就行了。

#include <bits/stdc++.h>
typedef long long LL;
using namespace std;

inline int rd() {
    char ch=getchar(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getchar();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=getchar();}
    return i*f;
}

const int N=2e5+50;
const LL INF=0x3f3f3f3f3f3f3f3f;
int n;
LL a[N],f[N][20],g[N];
LL dis[N],dep[N];
vector <int> l_leaf[N];
vector <int> r_leaf[N];
vector <int> son[N];
int main() {
    n=rd();
    for(int i=1;i<=n;i++) a[i]=rd();
    for(int i=1;i<n;i++) {
        int fa=(i+1)/2,now=i+1;
        dep[now]=dep[fa]+1;
        dis[now]=dis[fa]+rd();
        son[fa].push_back(now);
        if((now<<1)>n) {
            int lst=now,x=now>>1;
            while(x) {
                if(lst&1) r_leaf[x].push_back(now);
                else l_leaf[x].push_back(now);
                lst=x; x=x>>1;
            }
        }
    } 
    for(int i=n;i>=1;i--) {
        if(!son[i].size()) f[i][dep[i]]=0;
        else if(son[i].size()==1) {
            int lc=son[i][0]; LL v=(dis[lc]-dis[i])*a[lc];
            for(int e=l_leaf[i].size()-1;e>=0;e--) {
                int x=l_leaf[i][e]; f[x][dep[i]]=f[x][dep[i]+1]+v;
            }
        } else {
            int lc=son[i][0],rc=son[i][1];
            LL L_min=INF,R_min=INF,Lv=(dis[lc]-dis[i])*a[lc],Rv=(dis[rc]-dis[i])*a[rc];
            for(int e=l_leaf[i].size()-1;e>=0;e--) {
                int x=l_leaf[i][e]; L_min=min(L_min,f[x][dep[i]+1]+Lv+(dis[x]+dis[rc]-2*dis[i])*a[rc]);
            }
            for(int e=r_leaf[i].size()-1;e>=0;e--) {
                int x=r_leaf[i][e]; R_min=min(R_min,f[x][dep[i]+1]+Rv+(dis[x]+dis[lc]-2*dis[i])*a[lc]);
            }
            for(int e=l_leaf[i].size()-1;e>=0;e--) {
                int x=l_leaf[i][e]; f[x][dep[i]]=R_min+f[x][dep[i]+1];
            }
            for(int e=r_leaf[i].size()-1;e>=0;e--) {
                int x=r_leaf[i][e]; f[x][dep[i]]=L_min+f[x][dep[i]+1];
            }
        }
    } memset(g,0x3f,sizeof(g));
    for(int i=n;i>=1;i--) {
        if(son[i].size()) break;
        int now=i;
        while(dep[now]>=1) {
            g[now]=min(g[now],f[i][dep[now]]+((dep[now]>=2)?(dis[i]-dis[now>>2])*a[now>>2]:0));
            now>>=1;
        }
    } LL ans=INF;
    for(int i=n;i>=1;i--) {
        if(son[i].size()) break;
        ans=min(ans,f[i][0]);
    }
    for(int i=2;i<=n;i++) {
        LL rs=0,mx=INF;
        if(!son[i].size()) mx=(dis[i]-dis[i>>1])*a[i>>1];
        else {
            for(int e=l_leaf[i].size()-1;e>=0;e--) mx=min(mx,f[l_leaf[i][e]][dep[i]]+(dis[l_leaf[i][e]]-dis[i>>1])*a[i>>1]);
            for(int e=r_leaf[i].size()-1;e>=0;e--) mx=min(mx,f[r_leaf[i][e]][dep[i]]+(dis[r_leaf[i][e]]-dis[i>>1])*a[i>>1]);
        } rs+=mx; 
        int lst=i,now=i>>1;
        while(now) {
            if(!l_leaf[now].size() || !r_leaf[now].size()) {
                if(now!=1) rs+=(dis[now]-dis[now>>1])*a[now>>1];
            } else {
                int t=(son[now][0]==lst)?son[now][1]:son[now][0];
                rs+=(dis[t]-dis[now])*a[t];
                rs+=g[t];
            } lst=now; now=now>>1;
        }
        ans=min(ans,rs);
    } printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值