LOJ2009/bzoj4446 小凸玩密室 分步讲解

题目分析

现在我们用玩密室逃脱的思路来解决这题吧QWQ

1.寻找信息

1.密室
2.完全二叉树
3.在点灯的过程中,要保证任意时刻所有被点亮的灯泡必须连通
4.在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡
5.点一个节点的费用的计算方式和上一个点亮的节点有关

2.拟合信息

假设我们现在点完了以x为根的子树,由信息3,我们必须点亮x的父亲节点o。由信息4和2,我们下一步就必须点亮o的另一个儿子y,点完了y子树后,就要点亮o的父亲……
由信息5,我们dp的时候所需的状态一定要包括上一个节点是谁。
现在我们看我们要如何计算答案。可以想到先枚举最开始点亮的节点,然后模拟点灯过程。这时候我们需要一个这样的状态:g(x,i)先点以x为根的子树,再点x的位于第i层的祖先所花的费用。
于是计算答案是这样的:

    ans=g[1][0];
    for(i=2;i<=n;++i) {
        LL s=g[i][dep[i]-1];
        for(x=i;x>1;x>>=1) {
            y=x^1,o=x>>1;
            if(y>n) s+=b[o]*a[o>>1];//没有右儿子
            else s+=a[y]*b[y]+g[y][dep[o]-1];
        }
        if(s<ans) ans=s;
    }

3.分析所需信息

于是我们的目标是算出g(x,i)。
首先,x的位于第i层的祖先我们给它取名叫y。并用dis[x]表示从根节点到x的距离。
如果x是叶子节点,非常简单:g(x,i)=(dis[x]-dis[y])*a[y](只用计算点y的费用,点x的费用是在别的状态里计算的)。如果i=0则g(x,i)=0(第一次点灯)
可是如果x不是叶子节点呢?我们就需要先点完x,再去点x的兄弟,再点祖先啊!这样状态不好转移了。
于是想到我们还需要一个状态f(x,i):以x为根的子树刚刚点完后,去点第i层的兄弟的花费。(如图所示,要点的那个节点依然叫y)
QWQ
好,我们先不管f是怎么求的,先继续分析g。
有了f的帮助,我们就可以轻松写出求g的方程了!
如果x只有左儿子: g[x][i]=a[lson[x]]b[lson[x]]+g[lson[x]][i];
如果x有左右儿子:
g[x][i]=min(a[lson[x]]b[lson[x]]+f[lson[x]][dep[x]+1]+g[rson[x]][i],a[rson[x]]b[rson[x]]+f[rson[x]][dep[x]+1]+g[lson[x]][i]);
好长的状态转移方程QAQ…

    for(x=n;x>=1;--x)
        for(i=0;i<=dep[x];++i) {
        if((x<<1)>n) {
            y=x>>(dep[x]-i);
            if(i) g[x][i]=(dis[x]-dis[y])*a[y];
            else g[x][i]=0;
        }
        else if((x<<1)==n) g[x][i]=a[n]*b[n]+g[n][i];
        else {
            int l=(x<<1),r=(x<<1)|1,d=dep[x]+1;
            g[x][i]=min(a[l]*b[l]+f[l][d]+g[r][i],a[r]*b[r]+f[r][d]+g[l][i]);
        }
    }

4.搜寻所需信息

现在只差f没有求出来啦!怎么求QAQ
仔细想想就会发现,和求g的转移方程不是差不多吗?
至此,我们已经完美地从密室出逃解决此题。

    for(x=n;x>1;--x)
        for(i=2;i<=dep[x];++i) {
        if((x<<1)>n){
            o=x>>(dep[x]-i+1),y=(x>>(dep[x]-i))^1;
            f[x][i]=(dis[x]+dis[y]-dis[o]-dis[o])*a[y];
        }
        else if((x<<1)==n) f[x][i]=a[n]*b[n]+f[n][i];
        else {
            int l=(x<<1),r=(x<<1)|1,d=dep[x]+1;
            f[x][i]=min(a[l]*b[l]+f[l][d]+f[r][i],a[r]*b[r]+f[r][d]+f[l][i]);
        }
    }

代码

虽然写的长了一点,但是每一行都没有超过80列被怼线哦耶!

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define LL long long
LL read() {
    LL q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+(LL)(ch-'0'),ch=getchar();
    return q;
}
const int N=200005;
int n;LL ans;
LL a[N],b[N],f[N][19],g[N][19],dis[N],dep[N];
int main()
{
    int i,x,o,y;
    n=read();
    for(i=1;i<=n;++i) a[i]=read();dep[1]=1;
    for(i=2;i<=n;++i){
        b[i]=read();
        dis[i]=dis[i>>1]+b[i],dep[i]=dep[i>>1]+1;
    }
    for(x=n;x>1;--x)
        for(i=2;i<=dep[x];++i) {
        if((x<<1)>n){
            o=x>>(dep[x]-i+1),y=(x>>(dep[x]-i))^1;
            f[x][i]=(dis[x]+dis[y]-dis[o]-dis[o])*a[y];
        }
        else if((x<<1)==n) f[x][i]=a[n]*b[n]+f[n][i];
        else {
            int l=(x<<1),r=(x<<1)|1,d=dep[x]+1;
            f[x][i]=min(a[l]*b[l]+f[l][d]+f[r][i],a[r]*b[r]+f[r][d]+f[l][i]);
        }
    }
    for(x=n;x>=1;--x)
        for(i=0;i<=dep[x];++i) {
        if((x<<1)>n) {
            y=x>>(dep[x]-i);
            if(i) g[x][i]=(dis[x]-dis[y])*a[y];
            else g[x][i]=0;
        }
        else if((x<<1)==n) g[x][i]=a[n]*b[n]+g[n][i];
        else {
            int l=(x<<1),r=(x<<1)|1,d=dep[x]+1;
            g[x][i]=min(a[l]*b[l]+f[l][d]+g[r][i],a[r]*b[r]+f[r][d]+g[l][i]);
        }
    }
    ans=g[1][0];
    for(i=2;i<=n;++i) {
        LL s=g[i][dep[i]-1];
        for(x=i;x>1;x>>=1) {
            y=x^1,o=x>>1;
            if(y>n) s+=b[o]*a[o>>1];
            else s+=a[y]*b[y]+g[y][dep[o]-1];
        }
        if(s<ans) ans=s;
    }
    printf("%lld",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值