题意:给一棵树,且规定好了每个节点的父节点及子节点,并给出每个节点的值W(2<=W<=2^16),树的大小为n(2<=n<=2^16),对每一个节点s,求一个序列v1,v2,……,vm,使得:
1.v1=s,vi是vi-1的祖先;
2.这个序列使f(s)=Wv1+∑Wvi opt Wvi-1的值最大,其中opt是位运算和,或,异或三个运算中的一种,具体是哪个题目里会给出。
要求输出S=(∑i*f(i))mod(10^9+7)。(目前自己搞不来MathJax……公式这里有点乱,读者可以自己去原题地址看)
思路:一看就会有dp的想法,对一个点,找到其父节点中值最大的节点,进行运算,不断记录,不断运算。但是很烦恼的是这种思路的时间复杂度是O(n^2),明显会超时。看了题解后,知道了一个很巧妙的思想:设当前正在讨论的节点的值为W,基于W的值最大只有2^16,可以把W拆成两个部分:A,W二进制的前8位;B,W二进制的后8位。
用f[i][j]来表示当前节点的所有父节点中的W值的二进制的前8位为i的那些父节点,与j这个数进行opt运算后得到的dp最大值,如此,对每个点我们只需要枚举256次就ok了。
用dfs遍历这棵树不断更新,修改f数组,O(n*√n)就可出解。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long int LL;
const int MAXN=65540;
char op[10];
LL w[MAXN],v[256],f[256][256],h[MAXN][256],head[MAXN],nxt[MAXN];
LL ans;
inline LL opt(LL a,LL b){
if(op[0]=='A') return a&b;
if(op[0]=='O') return a|b;
return a^b;
}
void dfs(LL x){
LL dp=0,A=w[x]>>8,B=w[x]&255;
for(int i=0;i<256;++i) if(v[i]) dp=max(dp,f[i][B]+(opt(A,i)<<8));
ans=(1LL*x*(dp+w[x])+ans)%1000000007;
++v[A];
for(int i=0;i<256;++i) h[x][i]=f[A][i],f[A][i]=max(f[A][i],opt(B,i)+dp);
for(int i=head[x];i;i=nxt[i]) dfs(i);
--v[A];
for(int i=0;i<256;++i) f[A][i]=h[x][i];
}
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
LL x;
scanf("%d",&n);
scanf("%s",op);
for(int i=1;i<=n;++i) scanf("%u",w+i),head[i]=0;
for(int i=2;i<=n;++i) scanf("%u",&x),nxt[i]=head[x],head[x]=i;
ans=0;
dfs(1);
printf("%u\n",ans);
}
}