背景
“西瓜是可以种在平行世界的!”——小鸟游六花
描述
又到了收获时节,为了方便采摘,六花酱现在要给众西瓜命名,她已经为每个西瓜想好了一个名字,名字一共有m个属性,一个名字是否拥有这些属性可以用一个长度为m的01串xi来表示,表示这个名字是否吉利,是否好听等等。
她可以在任意时刻把这个名字倒过来(就像题目名字),倒过来后,原来好听的就不好听了,原来不好听的就好听了。即把这个01串取反。
每个属性都有一个价值vi,一个西瓜的价值为把它的名字拥有的所有属性的价值加起来。即第i个名字的价值为。其中xi,k表示第i个名字是否拥有第k个属性,0表示没有,1表示有。
当她手上拥有一堆西瓜时,这堆西瓜拥有第i个属性当且仅当存在一个西瓜拥有这一个属性,然后计算出的价值就是这堆西瓜的价值。即把所有西瓜的名字01串或起来之后当成一个西瓜处理。
现在六花开始采摘西瓜。瓜地构成一棵n个节点的树,一个点上有一个西瓜。她从根(1号节点)开始走,每次只能往儿子方向走。走到一个点时,她可以把这个西瓜采摘起来,每摘到一个西瓜,她可以把手头所有西瓜名字任意修改(修改即把名字倒过来)。
每个点会有一个价值,这个价值是她从根走到这个点,手上这堆西瓜价值的最大值(允许对西瓜们改名后的最大值)。一圈西瓜摘完,每个点的价值都已经确定。
摘完西瓜闲来无事,六花开始在西瓜地里漫步,她可以从任意点出发,在任意点结束(没有只能往儿子方向走的限制)。她不希望自己走过的相邻两个点的价值相同,同时不希望走过重复的点。她想知道,对于每个d,自己能走出的长度为d路径一共有多少条。这里的路径长度定义为路径上点的数量。
输入格式
第一行两个整数n,m。表示西瓜的个数(树的节点数量),属性的数量。
接下来n行,每行一个长度为m的01串,第i行的01串描述一个第i个西瓜的名字。
第n+2行包含m个正整数,第i个数vi表示第i个属性的价值。
再有n-1行,每行一个1到n中的整数,第i行的整数fai表示树中i+1号节点的父节点。
输出格式
如果能走出最大的路径长度为D,则输出D个整数,第i个数表示长度为i的路径数量。
样例输入
5 20
10110000001010011111
10010001110111101111
11111111111111111111
10000110011111110111
10110000001010011111
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1
1
2
3
样例输出
5 3 2 1
数据范围与约定
对于初: 1<= n<= 10,1<= m<= 20,1<= vi<= 109
对于续: 1<= n<= 105,1<= m<= 200,1<= vi<= 109,保证不存在节点的价值达到理论最大值,且树的深度不超过10。
对于终: 1<= n<= 105,1<= m<= 200,1<= vi<= 109
样例解释
1号点价值为10
2号点价值为17(1号西瓜名字倒过来,2号不变)
3号点价值为20
4号点价值为19(三个西瓜均不变即可)
5号点价值为20
长度为1的路径为5点单独一条路径
长度为2的路径为:4->2,2->1,1->3
长度为3的路径为:4->2->1,2->1->3
长度为4的路径为:4->2->1->3
备注
本题输入数据较多,请使用stdio.h读入。实测0.7s时限下,stdio.h+scanf需要的最大用时约0.4s。
—————————————————————————————————————————————
这道题 首先深度大于logm的点肯定能达到最大值。因为每次我们可以把至少一半的为0的属性变成1。
这个证明一下把
————————————————————————————————————————————————
因为我们考虑当前拥有的西瓜为x 要摘的西瓜为y
我们按二进制位考虑 因为x反转和y反转没有区别
考虑反转y 那么x是1的位不用考虑 剩下的0位 如果和y或起来不足一半 就反转y这样就大于一半了
————————————————————————————————————————————————
然后未到达最大值的点相邻两个肯定价值不同,也就是说路径相邻两个值不同的限制其实是把达到最大值的子树(不包括他本身)砍掉。
所以题目给的价值是没有用的 父亲和儿子只要不是全为0就不可能相同
这样我们只要考虑到每个点是否全为0就好了
我们发现 根到当前点 几个串可以组成一个矩阵
矩阵的行表示组成每个点的串 那么只要每一列都能有个1就能全0了
可是怎么判断一个矩阵能否达到上述状态呢
我们可以从亦或的角度考虑 反转一个序列就是整行异或1
那么问题就变成了找一个和列一样长的01串 使他和每一列异或后实现每一列都有1
那么由异或的性质得 A&A=0
所以如果所有列的排列(即不同的列数)达到2^k(k是到当前点收集到的点数) 那么所有的排列都在矩阵中了
所以不论你选什么 必然有一列为0 反之你随机选一个不在矩阵中存在的排列就可以了 QAQ
最后问题就转换成了求树上长度为d的路径的数量 然后我们考虑每一个点 算经过他的路径
就是一波树形dp了 233333
#include<stdio.h> #include<cstring> #include<algorithm> #define LL long long using namespace std; const int M=1e5+7; int read(){ int ans=0,f=1,c=getchar(); while(c<‘0‘||c>‘9‘){if(c==‘-‘) f=-1; c=getchar();} while(c>=‘0‘&&c<=‘9‘){ans=ans*10+(c-‘0‘); c=getchar();} return ans*f; } int n,m,k; char s[255]; int cnt,first[M],f[M][257]; int usd[2507],h[M]; LL ans[55],dis[M][55]; struct node{int to,next;}e[M]; void ins(int a,int b){e[++cnt]=(node){b,first[a]}; first[a]=cnt;} void dfs(int x,int d){ //printf("(%dL)",x); cnt=1<<d+1; for(int i=0;i<m;i++){ if(f[x][i]) h[i]|=(1<<d); if(usd[h[i]]!=x) cnt--,usd[h[i]]=x; } if(cnt) first[x]=0; for(int i=first[x];i;i=e[i].next) dfs(e[i].to,d+1); for(int i=0;i<m;i++) h[i]&=~(1<<d); //printf("(%dR)",x); } void dp(int x,int d){ ++dis[x][d]; for(int i=first[x];i;i=e[i].next){ int now=e[i].to; dp(now,d+1); for(int p=d;p<10;p++) for(int q=d;q<10;q++) ans[p+q-2*d]+=1LL*dis[x][p]*dis[now][q]; for(int p=0;p<10;p++) dis[x][p]+=dis[now][p]; } } int main(){ n=read(); m=read(); for(int i=1;i<=n;i++){ scanf("%s",s); for(int j=0;j<m;j++) f[i][j]=s[j]==‘1‘; } for(int i=1;i<=m;i++) read(); for(int i=2;i<=n;i++) k=read(),ins(k,i); dfs(1,0); dp(1,0); k=25; ans[0]=n; while(!ans[k]) k--; for(int i=0;i<=k;i++) printf("%lld ",ans[i]); return 0; }