一、题目
二、解法
在讲正解之前,这道题要踩三个点:
1、这棵树是一颗完全二叉树,所以很多地方可以简化。
2、要求已经点亮的灯要联通,感性理解就是灯堆在一起。
3、要求点亮子树之后才能点亮其他点,这就启示我们可以用树
d
p
dp
dp。
0x01 暴力
第一种方法就是全排,但是检查很麻烦,所以不推荐,还是可以拿到
10
p
t
s
10pts
10pts。
更好的暴力避免了检查,枚举起点后暴力枚举子树内的情况,时间复杂度
O
(
2
n
.
.
.
)
O(2^n...)
O(2n...),拿到
20
p
t
s
20pts
20pts。
然后就可以跳过这道题了
0x02 正解
考虑树形
d
p
dp
dp,设
d
p
[
u
]
[
i
]
dp[u][i]
dp[u][i]为从
u
u
u点出发(假设
u
u
u点已点亮,不考虑花费),把子树全部点亮,然后从
i
i
i点结束的最小花费(因为我们需要算距离),状态转移为:
(
a
[
i
]
a[i]
a[i]为点
i
i
i的权值,
b
[
i
]
b[i]
b[i]为父亲到
i
i
i的边权,
d
i
s
dis
dis为距离)
{ d p [ u ] [ i ] = d p [ 2 u ] [ j ] + a [ 2 u ] × b [ 2 u ] + ( d i s [ u ] [ j ] + b [ 2 u + 1 ] ) × a [ 2 u + 1 ] + d p [ 2 u + 1 ] [ i ] d p [ u ] [ i ] = d p [ 2 u + 1 ] [ j ] + a [ 2 u + 1 ] × b [ 2 u + 1 ] + ( d i s [ u ] [ j ] + b [ 2 u ] ) × a [ 2 u ] + d p [ 2 u ] [ i ] \begin{cases} dp[u][i]=dp[2u][j]+a[2u]\times b[2u]+(dis[u][j]+b[2u+1])\times a[2u+1]+dp[2u+1][i]\\ dp[u][i]=dp[2u+1][j]+a[2u+1]\times b[2u+1]+(dis[u][j]+b[2u])\times a[2u]+dp[2u][i] \end{cases} {dp[u][i]=dp[2u][j]+a[2u]×b[2u]+(dis[u][j]+b[2u+1])×a[2u+1]+dp[2u+1][i]dp[u][i]=dp[2u+1][j]+a[2u+1]×b[2u+1]+(dis[u][j]+b[2u])×a[2u]+dp[2u][i]
第一个转移是当
i
i
i在右儿子时的转移,第二个是在左儿子的转移。
观察
d
p
dp
dp式,发现对于每一个
i
i
i,对应的
j
j
j的取值是不变的,所以我们可以先算固定的那一部分,这样转移就能做到均摊
O
(
1
)
O(1)
O(1),考虑可能的状态数,发现它很像线段树,每一层覆盖一遍线段(这里是叶子),所以状态数是
O
(
n
log
n
)
O(n\log n)
O(nlogn),时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
但是还有一个问题?怎么确定最初的起点,我们这么算只能知道起点为
1
1
1的答案。
还是考虑树形
d
p
dp
dp,设
t
[
u
]
[
i
]
t[u][i]
t[u][i]为起点在子树
u
u
u内,把子树搞定,最后在
i
i
i作结的最小花费,我们并不需要关心起点具体是什么,因为我们不需要用起点来算答案,则有:
{ t [ u ] [ i ] = t [ 2 u ] [ j ] + d i s [ u ] [ j ] × a [ u ] + a [ 2 u + 1 ] × b [ 2 u + 1 ] + d p [ 2 u + 1 ] [ i ] t [ u ] [ i ] = t [ 2 u + 1 ] [ j ] + d p [ u ] [ j ] × a [ u ] + a [ 2 u ] × b [ 2 u ] + d p [ 2 u ] [ i ] \begin{cases} t[u][i]=t[2u][j]+dis[u][j]\times a[u]+a[2u+1]\times b[2u+1]+dp[2u+1][i]\\ t[u][i]=t[2u+1][j]+dp[u][j]\times a[u]+a[2u]\times b[2u]+dp[2u][i] \end{cases} {t[u][i]=t[2u][j]+dis[u][j]×a[u]+a[2u+1]×b[2u+1]+dp[2u+1][i]t[u][i]=t[2u+1][j]+dp[u][j]×a[u]+a[2u]×b[2u]+dp[2u][i]
发现这个式子可以用更上面一样的优化,时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn),由于我们没有考虑
u
u
u为根的情况,要用
d
p
dp
dp来更新
t
t
t。
还有一个问题,虽然状态数达不到,但是空间要开
n
2
n^2
n2的,所以我们用
v
e
c
t
o
r
vector
vector来存状态,动态开状态,很多问题也能一并解决。
#include <cstdio>
#include <vector>
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int MAXN = 200005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,a[MAXN],b[MAXN],dep[MAXN];
vector<int> dp1[MAXN],dp2[MAXN],dis[MAXN];
void dfs(int u,int fa)
{
if(2*u<=n)
{
dep[2*u]=dep[u]+b[2*u];
dfs(2*u,u);//左子树
int t=dp1[2*u].size();//get size
if(2*u+1<=n)
{
dep[2*u+1]=dep[u]+b[2*u+1];//距离
dfs(2*u+1,u);//右子树
int Ml1=inf,Ml2=inf,Mr1=inf,Mr2=inf;
for(int i=0;i<dp1[u].size();i++)//优化
{
if(i<t)//看枚举的终点在左子树还是右子树
{
Ml1=min(Ml1,dp1[2*u][i]+a[2*u]*b[2*u]+(dis[u][i]+b[2*u+1])*a[2*u+1]);
Ml2=min(Ml2,dp2[2*u][i]+dis[u][i]*a[u]+b[2*u+1]*a[2*u+1]);
}
else
{
Mr1=min(Mr1,dp1[2*u+1][i-t]+a[2*u+1]*b[2*u+1]+(dis[u][i]+b[2*u])*a[2*u]);
Mr2=min(Mr2,dp2[2*u+1][i-t]+dis[u][i]*a[u]+b[2*u]*a[2*u]);
}
}
for(int i=0;i<dp1[u].size();i++)
{
if(i<t)//更新
{
dp1[u][i]=Mr1+dp1[2*u][i];
dp2[u][i]=min(dp1[u][i],Mr2+dp1[2*u][i]);
}
else
{
dp1[u][i]=Ml1+dp1[2*u+1][i-t];
dp2[u][i]=min(dp1[u][i],Ml2+dp1[2*u+1][i-t]);
}
}
}
else//特判只有一个左儿子,这种情况只会有一种,且在最下面
{
for(int i=u;i>=1;i/=2)//当前点可作为状态
{
dp1[i].push_back(0);
dp2[i].push_back(0);
dis[i].push_back(dep[u]-dep[i]);
}
dp1[u][0]=a[2*u]*b[2*u];
dp2[u][0]=dp1[u][0];
dp1[u][1]=inf;
dp2[u][1]=a[u]*b[2*u];
}
}
else
{
for(int i=u;i>=1;i/=2)//动态开店
{
dp1[i].push_back(0);
dp2[i].push_back(0);
dis[i].push_back(dep[u]-dep[i]);
}
}
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=2;i<=n;i++)
b[i]=read();
dfs(1,0);
int ans=inf;
for(int i=0;i<dp2[1].size();i++)
ans=min(ans,dp2[1][i]);
printf("%lld\n",ans);
}
自闭了,我树形还是太差了,一定要先看懂题在想。
tc:我读完3遍题就可以口算了