#2009. 「SCOI2015」小凸玩密室
阿新 • • 發佈:2018-11-26
神仙題啊。完全想不出
首先看方案。可以從任意一個點開始,在這個點要先走完子樹,然後走到父親,再走兄弟,再走父親的父親,父親的兄弟。。一直走到1,1的另外一個子樹,結束。
完全不會鴨.jpg
設f[i][j]是走完i的子樹,再走到i的第j個祖先的最小花費。那麼上面的方案可以表示成:f[x][1](走完x的子樹再走到x父親)+(x父親~x兄弟)+f[(x兄弟)][1](x兄弟走完了走到x父親的父親)+...
那麼怎麼求f呢,感覺不是很好求
再來一個:g[i][j]是走完i的子樹,再走到i的第j個祖先的兄弟的最小花費。
那麼就很好轉移了,分情況討論即可,不難,見程式碼
#include<bits/stdc++.h> #define il inline #define vd void typedef long long ll; il int gi(){ int x=0,f=1; char ch=getchar(); while(!isdigit(ch)){ if(ch=='-')f=-1; ch=getchar(); } while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } ll a[200010],b[200010],dep[200010]; ll w[200010][19];//w[i][j]是i到i的第j個祖先的距離 ll f[200010][19],g[200010][19]; int main(){ #ifndef ONLINE_JUDGE freopen("4253.in","r",stdin); freopen("4253.out","w",stdout); #endif int n=gi(); for(int i=1;i<=n;++i)a[i]=gi(); for(int i=2;i<=n;++i)b[i]=gi(); for(int i=1;i<=n;++i){ dep[i]=dep[i>>1]+1; w[i][1]=b[i]; for(int j=2;j<=dep[i] ;++j) w[i][j]=w[i>>1][j-1]+b[i]; } for(int i=n;i;--i){ for(int j=0;j<=dep[i];++j){ if((i<<1|1)<=n){// 2個兒子,要列舉先走到哪一個兒子去 f[i][j]=std::min(g[i<<1][0]+b[i<<1]*a[i<<1]+f[i<<1|1][j+1],g[i<<1|1][0]+b[i<<1|1]*a[i<<1|1]+f[i<<1][j+1]); g[i][j]=std::min(g[i<<1][0]+b[i<<1]*a[i<<1]+g[i<<1|1][j+1],g[i<<1|1][0]+b[i<<1|1]*a[i<<1|1]+g[i<<1][j+1]); }else if((i<<1)<=n){// 1個兒子,直接走到這個兒子然後繼續 f[i][j]=f[i<<1][j+1]+b[i<<1]*a[i<<1]; g[i][j]=g[i<<1][j+1]+b[i<<1]*a[i<<1]; }else{// 葉子,子樹走完了可以直接跳到j所指向的點 f[i][j]=w[i][j]*a[i>>j]; g[i][j]=(w[i][j+1]+b[(i>>j)^1])*a[(i>>j)^1]; } } } ll ans=2e18; for(int i=1;i<=n;++i){//列舉起點 ll res=f[i][1];//先走完i的子樹,然後走到i的父親 for(int j=i>>1,lst=i;j;lst=j,j>>=1){ if((lst^1)<=n)res+=b[lst^1]*a[lst^1]+f[lst^1][2]; else res+=b[j]*a[j>>1]; } ans=std::min(ans,res); } printf("%lld\n",ans); return 0; }