LUOGU P4253 [SCOI2015]小凸玩密室(树形dp)
解题思路
玄学树形\(dp\),题目描述极其混乱。。。看错了两次题,设首先根据每次必须点完子树里的灯才能点别的,那么点灯情况只有两种,第一种是点到某一个祖先,第二种是点到某一个祖先的兄弟。所以可以设出状态\(f[i][j][0/1]\)表示以\(i\)为根的子树已经点完,\(0\)表示下一步点到\(j\)祖先,\(1\)表示下一步点到\(j\)祖先的兄弟。然后转移的时候讨论一下这个点有几个儿子。最后算答案时枚举起点,然后一步一步往上跳,有兄弟的跳兄弟。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define int long long
using namespace std;
const int MAXN =200005;
inline int rd(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {f=ch=='-'?0:1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
int n,a[MAXN],f[MAXN][30][2],dis[MAXN][30],ans=1e18,ansN;
inline int p(int i,int j){
return ((1<<(j-1))<=i)?(i>>j):-1;
}
inline int b(int i,int j){
return ((i>>(j-1))^1);
}
signed main(){
n=rd();dis[1][1]=0;
for(int i=1;i<=n;i++) a[i]=rd();
for(int i=2;i<=n;i++){
dis[i][1]=rd();
for(int j=2;~p(i,j);j++)
dis[i][j]=dis[p(i,1)][j-1]+dis[i][1];
}
for(int i=n;i;i--)
for(int j=1;~p(i,j);j++){
if((i<<1)>n) {
f[i][j][0]=dis[i][j]*a[p(i,j)];
f[i][j][1]=(dis[i][j]+dis[b(i,j)][1])*a[b(i,j)];
}
else if((i<<1|1)>n){
f[i][j][0]=f[i<<1][j+1][0]+dis[i<<1][1]*a[i<<1];
f[i][j][1]=f[i<<1][j+1][1]+dis[i<<1][1]*a[i<<1];
}
else {
f[i][j][0]=min(f[i<<1][j+1][0]+f[i<<1|1][1][1]+dis[i<<1|1][1]*a[i<<1|1],
f[i<<1|1][j+1][0]+f[i<<1][1][1]+dis[i<<1][1]*a[i<<1]);
f[i][j][1]=min(f[i<<1][j+1][1]+f[i<<1|1][1][1]+dis[i<<1|1][1]*a[i<<1|1],
f[i<<1|1][j+1][1]+f[i<<1][1][1]+dis[i<<1][1]*a[i<<1]);
}
}
for(int i=1;i<=n;i++){
ansN=f[i][1][0];
for(int j=p(i,1),last=i;~j;j=p(j,1),last=p(last,1)){
if(b(last,1)<=n)
ansN+=dis[b(last,1)][1]*a[b(last,1)]+f[b(last,1)][2][0];
else
ansN+=dis[j][1]*a[p(j,1)];
}
ans=min(ans,ansN);
}cout<<ans;
return 0;
}