小凸玩密室
题解
神仙树形dp。我们定义为点亮i后回到i的第j个最小的最小值,为i的第j个最小的另一个儿子的最小值。之后我们一次遍历整棵树,更新其的dp值,统计答案时再将整个过程的值累加即可。
因为这是一棵完全二叉树,所以我们可以将其从n到1来遍历,不用dfs来遍历,也可达到一样的效果。
源码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<stack>
#define MAXN 200005
using namespace std;
typedef long long LL;
#define int LL
const LL INF=0x7f7f7f7f7f7f7f7f;
LL n,a[MAXN],b[MAXN],dis[MAXN][20];
LL dp[MAXN][20],g[MAXN][20];
int ls[MAXN],rs[MAXN];
#define gc() getchar()
template<typename _T>
inline void read(_T &x)
{
_T f=1;x=0;char s=gc();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=gc();}
while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=gc();}
x*=f;
}
int bro(int k,int x)
{
return (k>>x-1)^1;
}
LL solve()
{
LL res=INF;
for(int k=n;k>0;k--)
{
if(!ls[k])
for(int i=1;k>>(i-1);i++)
g[k][i]=(dis[k][i]+dis[bro(k,i)][1])*a[bro(k,i)];
else if(!rs[k])
for(int i=1;k>>(i-1);i++)
g[k][i]=dis[ls[k]][1]*a[ls[k]]+g[ls[k]][i+1];
else
for(int i=1;k>>(i-1);i++)
g[k][i]=min(dis[ls[k]][1]*a[ls[k]]+g[ls[k]][1]+g[rs[k]][i+1],dis[rs[k]][1]*a[rs[k]]+g[rs[k]][1]+g[ls[k]][i+1]);
}
for(int k=n;k>0;k--)
{
if(!ls[k])
for(int i=1;k>>(i-1);i++)
dp[k][i]=dis[k][i]*a[k>>i];
else if(!rs[k])
for(int i=1;k>>(i-1);i++)
dp[k][i]=dp[ls[k]][i+1]+dis[ls[k]][1]*a[ls[k]];
else
for(int i=1;k>>(i-1);i++)
dp[k][i]=min(dis[ls[k]][1]*a[ls[k]]+g[ls[k]][1]+dp[rs[k]][i+1],dis[rs[k]][1]*a[rs[k]]+g[rs[k]][1]+dp[ls[k]][i+1]);
}
for(int k=1;k<=n;k++)
{
LL sum=dp[k][1];
for(int i=1,fa=k>>1;fa;i++,fa>>=1)
{
int br=bro(k,i);
if(br>n) sum+=dis[fa][1]*a[fa>>1];
else sum+=dis[br][1]*a[br]+dp[br][2];
}
res=min(res,sum);
}
return res;
}
signed main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=2;i<=n;i++) read(b[i]);
for(int i=1;i<=(n>>1)+1;i++)
{
if((i<<1)<=n) ls[i]=i<<1;
else break;
if((i<<1|1)<=n) rs[i]=i<<1|1;
}
for(int i=2;i<=n;i++)
dis[i][1]=b[i];
for(int i=2;i<=18;i++)
for(int j=n;j>>i;j--)
dis[j][i]=dis[j][i-1]+dis[j>>(i-1)][1];
printf("%lld",solve());
return 0;
}