[BZOJ4446]SCoi2015 小凸玩密室 树形DP(烧脑高能预警)
4446: [Scoi2015]小凸玩密室
Time Limit: 10 Sec Memory Limit: 128 MBDescription
Input
Output
Sample Input
5 1 2
2 1
Sample Output
HINT
对于100%的数据,1≤N≤2×105,1<Ai,Bi≤10^5
(其实我是回来补暑假没写完的题解的)
题解:
(首先我经过激烈的思想斗争,认为那个点亮灯泡的花费计算只与上一个有关(其实是因为前4个太难考虑了...)
事实证明这样做是对的..就是只考虑上一个:(
有了之前做非线性DP的经验,我一开始想的还是合并类型
但是发现数据范围不太对....这似乎是一个介于O(n)和O(n2)范围内的DP
我们考虑怎么定义状态,以及怎么转移.
一开始我想的是f[i][j]表示"走完以i为根的子树之后走去j点的最小花费"
但是我发现这个MLE了,2e5开不下
但是这又是一颗完全二叉树,所以我们考虑能不能应用他的一些性质
我们观察到,点灯泡的起点没有确定,但是题目有这样的两个限制:
要保证任意时刻所有被点亮的灯泡必须连通.
在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡
这样的话,某一个点被点亮的时候只能有三种转移的情况:从儿子走来,从兄弟走来和从父亲走来(废话)
因此,当某一个子树被完全点亮之后,我们就要去某一个他的祖先,或者是他某个祖先的儿子
我们发现上面这两个都与这个节点的祖先有关(这里我们把自己也看成自己的祖先)
由于这是一棵二叉树,我们完全可以通过位运算(左移,右移和异或)来计算出某个节点的某个深度的祖先.
这样,第二维完全不用是O(n)的:我们可以把第二维设为深度,即走完以i为根的子树之后走去深度为j的祖先节点的“XXXX”的最小花费
那么我们再考虑一下:我们这个点的祖先节点可能已经被点亮,也可能没有被点亮。如果已经点亮,我们就必须去点亮祖先的兄弟那棵子树。
因此我们定义两个数组:
f(ather)[i][j]表示走完以i为根的子树之后去点亮深度为j的祖先节点的最小花费
b(rother)[i][j]表示走完以i为根的子树之后去点亮深度为j的祖先节点的兄弟节点的最小花费
接下来我们考虑状态的转移。最好想到的是节点i是叶子节点的情况:直接走去对应的节点即可。
完整代码见下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int N=200010; 6 typedef long long LL; 7 int n,l[N],r[N],deep[N]; 8 LL f[N][19],b[N][19],a[N],lval[N],rval[N],dis[N]; 9 int main() 10 { 11 scanf("%d",&n);LL v;int rt;deep[1]=1; 12 for(int i=1;i<=n;i++)scanf("%lld",&a[i]); 13 for(int i=1;i<n;i++) 14 { 15 scanf("%lld",&v); 16 rt=(i+1)>>1,deep[i+1]=deep[rt]+1; 17 if((i+1)&1) r[rt]=i+1,rval[rt]=v,dis[r[rt]]=dis[rt]+rval[rt]; 18 else l[rt]=i+1,lval[rt]=v,dis[l[rt]]=dis[rt]+lval[rt]; 19 } 20 for(int i=n;i>1;i--) 21 for(int j=2;j<=deep[i];j++) 22 if(!r[i]) 23 if(!l[i]) 24 { 25 int fa=i>>(deep[i]-j+1),fab=(i>>(deep[i]-j))^1; 26 b[i][j]=( dis[i]+dis[fab]-(dis[fa]<<1) )*a[fab]; 27 } 28 else b[i][j]=lval[i]*a[l[i]]+b[l[i]][j]; 29 else b[i][j]=min(lval[i]*a[l[i]]+b[l[i]][deep[i]+1]+b[r[i]][j],rval[i]*a[r[i]]+b[r[i]][deep[i]+1]+b[l[i]][j]); 30 for(int i=n;i;i--) 31 for(int j=0;j<=deep[i];j++) 32 { 33 if(!r[i]) 34 if(!l[i]) 35 if(!j)f[i][j]=0; 36 else 37 { 38 int fa=i>>(deep[i]-j); 39 f[i][j]=(dis[i]-dis[fa])*a[fa]; 40 } 41 else f[i][j]=lval[i]*a[l[i]]+f[l[i]][j]; 42 else f[i][j]=min(lval[i]*a[l[i]]+b[l[i]][deep[i]+1]+f[r[i]][j],rval[i]*a[r[i]]+b[r[i]][deep[i]+1]+f[l[i]][j]); 43 } 44 LL ans=f[1][0]; 45 for(int i=2;i<=n;i++) 46 { 47 int u=i,bro=u^1; 48 LL tmp=f[u][deep[u]-1]; 49 while(u>1) 50 { 51 if(bro>n)tmp+=a[u>>2]*(dis[u>>1]-dis[u>>2]); 52 else tmp+=a[bro]*(dis[bro]-dis[u>>1])+f[bro][deep[u>>1]-1]; 53 u>>=1,bro=u^1; 54 } 55 ans=min(ans,tmp); 56 } 57 printf("%lld\n",ans); 58 }