洛谷P4253:[SCOI2015]小凸玩密室 (DP)

题目传送门:https://www.luogu.org/problemnew/show/P4253


题目分析:模拟赛的时候因为T2的锅,就没仔细想这题,写了个20pts的暴力状压还错了。后来改的时候发现我的code在跳到当前节点i的第一个子树未满的祖先j时,忘记查看j自身是否被点亮了QAQ。

这题的正解其实也不难,主要是一些细节比较烦。由于题面给出的本身就是棵二叉树,可以直接记 f[node][i] f [ n o d e ] [ i ] 表示以node为第一个点亮的节点,并且最后点亮的是node子树中的叶子结点i时,点亮node子树的最小花费。很明显f数组的总大小是 nlog(n) n log ⁡ ( n ) 的。合并的时候在node的Lson中找一个i使得 f[Lson][i]+Len(i,Rson)a[Rson] f [ L s o n ] [ i ] + L e n ( i , R s o n ) ∗ a [ R s o n ] 最小,加上 Len(node,Lson)a[Lson] L e n ( n o d e , L s o n ) ∗ a [ L s o n ] ,记为temp1。然后用每个f[Rson][x]加上temp1即变为f[node][x]。对Lson中每个叶子结点x的处理也同理。这样维护f的时间就是 O(nlog(n)) O ( n log ⁡ ( n ) )

统计答案也类似。对每个节点维护g[node][0],表示从node的左子树中选一个x使得 Len(node,Lson)a[Lson]+f[Lson][x]+Len(x,fa)a[fa] L e n ( n o d e , L s o n ) ∗ a [ L s o n ] + f [ L s o n ] [ x ] + L e n ( x , f a ) ∗ a [ f a ] 最小的值,其中fa是node的父亲。右子树同理,用g[node][1]维护。g数组的维护也是 O(nlog(n)) O ( n log ⁡ ( n ) ) 的,并且维护出来之后就能枚举初始点亮的节点统计答案了。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;

#define P pair<int,long long>
#define MP(x,y) make_pair(x,(long long)y)

const int maxn=200100;
const long long oo=1e17;
typedef long long LL;

vector <P> f[maxn];
LL g[maxn][2];

int a[maxn];
int b[maxn];
int n;

LL Len(int x,int y)
{
    if (x==y) return 0;
    if (x<y) swap(x,y);
    return Len(x>>1,y)+b[x];
}

int main()
{
    freopen("D.in","r",stdin);
    freopen("D.out","w",stdout);

    scanf("%d",&n);
    for (int i=1; i<=n; i++) scanf("%d",&a[i]);
    for (int i=2; i<=n; i++) scanf("%d",&b[i]);

    for (int i=n; i>=1; i--)
    {
        int x=i<<1,y=x|1;
        if (x>n) f[i].push_back( MP(i,0) );
        if (x==n) f[i].push_back( MP(x, Len(i,x)*a[x] ) );
        if (x<n)
        {
            LL temp1=oo,temp2=oo;
            for (int j=0; j<f[x].size(); j++)
                temp1=min(temp1, f[x][j].second+Len(f[x][j].first,y)*a[y] );
            for (int j=0; j<f[y].size(); j++)
                temp2=min(temp2, f[y][j].second+Len(f[y][j].first,x)*a[x] );
            temp1+=Len(i,x)*a[x];
            temp2+=Len(i,y)*a[y];

            for (int j=0; j<f[y].size(); j++)
                f[i].push_back( MP(f[y][j].first,f[y][j].second+temp1) );
            for (int j=0; j<f[x].size(); j++)
                f[i].push_back( MP(f[x][j].first,f[x][j].second+temp2) );
        }
    }

    for (int i=n; i>=1; i--)
    {
        g[i][0]=g[i][1]=oo;
        int x=i<<1,y=x|1,fa=i>>1;
        if (x==n)
            g[i][0]=Len(i,x)*a[x]+Len(x,fa)*a[fa],g[i][1]=Len(i,fa)*a[fa];
        if (x<n)
        {
            for (int j=0; j<f[x].size(); j++)
            g[i][0]=min(g[i][0],f[x][j].second+Len(f[x][j].first,fa)*a[fa]);
            for (int j=0; j<f[y].size(); j++)
            g[i][1]=min(g[i][1],f[y][j].second+Len(f[y][j].first,fa)*a[fa]);
            g[i][0]+=Len(i,x)*a[x];
            g[i][1]+=Len(i,y)*a[y];
        }
    }

    LL ans=oo;
    for (int i=n; i>=1; i--)
    {
        int j=i>>1,x=i;
        LL temp=oo;
        for (int k=0; k<f[i].size(); k++)
            temp=min(temp,f[i][k].second+Len(f[i][k].first,j)*a[j]);
        while (j)
        {
            temp+=g[j][(x&1)^1];
            x=j,j>>=1;
        }
        ans=min(ans,temp);
    }
    printf("%lld\n",ans);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值