MC0483过园数统计
📅 2026/7/4 3:14:49
👁️ 阅读次数
📝 编程学习
MC0483过园数统计
大观园内众姐妹成立了诗社。各社员居所相连正如一棵树,居所的编号为1∼n1∼n。各社员均有一个编号,或为诗社核心成员(记为 1),或为列席成员(记为 0)。
若以某姐妹的居所为 “社集之地”(即视其为根),自社集之地至另一居所须过几处园门,便称为那居所的 “过园数”(即根到该点的边数)。
现在做为社长的小码妹需要知道:对于园中每处居所i(1≤i≤n)i(1≤i≤n),若以该点为社集之地时,所有诗社核心成员(即记为1者)的居所,其 “过园数” 共有多少种不同的数值?
格式
输入格式:
第一行一个整数n(1≤n≤3×104)n(1≤n≤3×104)。
第二行nn个整数a1∼an(0≤ai≤1)a1∼an(0≤ai≤1)。
接下来n−1n−1行,每行两个整数u,v(1≤u,v≤n)u,v(1≤u,v≤n),表示点uu和点vv之间有一条边。
输出格式:
输出nn行,其中第ii行代表当点ii为根节点时的答案。
样例 1
输入:
3 0 1 1 1 2 1 3
复制
输出:
1 2 2
复制
样例 2
输入:
6 0 0 1 1 1 1 1 2 1 3 1 4 2 5 3 6
复制
输出:
2 3 4 3 3 4
复制
备注
样例11解释:
当11号点为根节点时,值为11的点(22号点和33号点)到达11号点的距离分别为1,11,1,一共有11种不同的数值。
当22号点为根节点时,值为11的点(22号点和33号点)到达22号点的距离分别为0,20,2,一共有22种不同的数值。
当33号点为根节点时,值为11的点(22号点和33号点)到达33号点的距离分别为2,02,0,一共有22种不同的数值。
代码1(超时)
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.*; public class Main { static int N=3*10010; static int a[]=new int[N]; static boolean f[]=new boolean[N]; static List<Integer>[] adj=new List[N]; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out)); StringTokenizer st=new StringTokenizer(br.readLine()); int n=Integer.parseInt(st.nextToken()); st=new StringTokenizer(br.readLine()); for (int i = 1; i <=n; i++) { a[i]=Integer.parseInt(st.nextToken()); adj[i]=new ArrayList<>(); } for (int i = 1; i < n; i++) { st=new StringTokenizer(br.readLine()); int a=Integer.parseInt(st.nextToken()),b=Integer.parseInt(st.nextToken()); adj[a].add(b); adj[b].add(a); } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { f[j]=false; } int dis[]=new int[n+1]; dis[i]=0; Set<Integer> set=new HashSet<>();//有多少不同的边数 if(a[i]==1)set.add(0); Queue<Integer> queue=new LinkedList<>(); queue.add(i);f[i]=true; while(!queue.isEmpty()){ int top=queue.poll(); for(int son:adj[top]){ if(f[son]==true)continue;//相当于是根节点跳过 dis[son]=dis[top]+1; if(a[son]==1)set.add(dis[son]); queue.add(son); f[son]=true; } } System.out.println(set.size()); } br.close(); bw.flush(); bw.close(); } }代码2
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.*; public class Main { static int N=3*10010,n,words;//words表示需要多少的long块来记录 static int a[]=new int[N]; static int ans[]=new int[N]; static long f[][];//表示以i为根节点 所有核心店到i的距离 //比如 f[u] 中 第一个long 表示距离0-63 是否存在 存在则是1 不存在则是0 0..1010 表示距离1、2存在 //第二个long 表示距离64-127 是否存在 存在则是1 不存在则是0 0..1001 表示距离 64、67存在 static List<Integer>[] adj=new List[N];//邻接表 public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out)); StringTokenizer st=new StringTokenizer(br.readLine()); n=Integer.parseInt(st.nextToken()); words=(n+63)/64; f=new long[N][words]; st=new StringTokenizer(br.readLine()); for (int i = 1; i <=n; i++) { a[i]=Integer.parseInt(st.nextToken()); adj[i]=new ArrayList<>(); } for (int i = 1; i < n; i++) { st=new StringTokenizer(br.readLine()); int a=Integer.parseInt(st.nextToken()),b=Integer.parseInt(st.nextToken()); adj[a].add(b); adj[b].add(a); } dfs1(1,0);//以 1 为根的树形下,f[u] 的含义就是“u 子树内的所有核心点到 u 的距离集合”。 dfs2(1,0,new long[words]);//不断的换根 求过圆数 开始时 1的外部节点 也就是向上的点集合到1为空集 StringBuilder stringBuilder=new StringBuilder(); for (int i = 1; i <= n; i++) { stringBuilder.append(ans[i]); stringBuilder.append("\n"); } bw.write(stringBuilder.toString().trim()); br.close(); bw.flush(); bw.close(); } static void dfs1(int u,int p){//计算以每个节点为根 该子树中核心点到根节点的边数 if(a[u]==1){//自己到自己 距离为0 setBit(f[u],0); } for(int v:adj[u]){ if(v==p)continue; dfs1(v,u); or(f[u],shiftLeft(f[v],1));//合并 } } static void dfs2(int u,int p,long upSet[]){//不断的换根 求出最终结果 //upSet表示根节点向上的外面的核心点距离集合 long all[]=Arrays.copyOfRange(f[u], 0,words); or(all,upSet); for (int i = 0; i < words; i++) { ans[u]+=Long.bitCount(all[i]);//bigCount统计1的个数 } //以下全部都是为换根做准备的 // 比如u的三个叶子结点是 v1 v2 v3 v4 v5 //当v3作为根的时候 我们需要求出v1-3 的所有或的距离的结果 以及v4-5 的所有或的距离的结果 //如果每次都把前面和后面的距离全部或起来 复杂度很高 //我们可以预处理出前缀或 以及 后缀或 //统计孩子结点 这里要有索引区分子树 因为后续要用索引计算前缀 后缀或 int k=0; List<Integer> list=new ArrayList<>(); for(int v:adj[u]){ if(v==p)continue; k++; list.add(v); } if(k==0)return; //计算每个孩子节点(子树)到u的距离 long g[][]=new long[k][words];//表示第i课子树的核心点到u的距离的集合 for (int i = 0; i < k; i++) { g[i]=shiftLeft(f[list.get(i)], 1); } long prefix[][]=new long[k][words];//前缀或 prefix[0]=g[0]; for (int i = 1; i < k; i++) { or(prefix[i],prefix[i-1]); or(prefix[i],g[i]); } long suffi[][]=new long[k][words];//后缀或 suffi[k-1]=g[k-1]; for (int i = k-2; i>=0; i--) { or(suffi[i], suffi[i+1]); or(suffi[i], g[i]); } //依次把每个孩子节点当成根 //当一个孩子结点变为根的时候 它的upSet集合就变成了 我这个孩子结点之前的所有结点 还有u //还有我这个孩子结点之后的所有结点 还有传进来的upSet for (int i = 0; i < k; i++) { int v=list.get(i); //v的upSet先计算成到u的距离 最后统一加1 long other[]=new long[words]; if(a[u]==1)setBit(other, 0); or(other,upSet); if(i-1>=0)or(other, prefix[i-1]); if(i+1<k)or(other, suffi[i+1]); long newUp[]= shiftLeft(other, 1); dfs2(v,u,newUp); } } static void or(long fina[],long src[]){ for (int i = 0; i < words; i++) { fina[i]|=src[i]; } } static long[] shiftLeft(long t[],int x){//将t中所有的距离加上x long k[]=new long[words]; if((x&63)==0){ int change = (x>>6); for (int i = 0; i < words; i++) { if(i+change<words)k[i+change]=t[i]; } }else{ for (int i = 0; i < words; i++) { long w=t[i]; int id=i+(x>>6); if(id<words)k[id]|= w<<(x&63); if(id+1<words)k[id+1]|= w>>>(64-(x&63)); // >> 是算术右移,如果 w 的最高位是 1,右移后左边会补 1(符号位),导致结果错误。 // 位图操作要求逻辑右移,即左边一律补 0,必须用 >>>。 } } return k; } static void setBit(long t[],int d){//将数组表示的d的距离设置为1 t[d>>6]|= (1L<<(d & 63));//t[d/64]|= (1<<(d % 64)) #必须用 1L 1 是 int 类型,只有 32 位 //>> 6 相当于除以 64(因为 2⁶ = 64) d >> 6 是找到距离 d 应该放在哪个 long 里 //& 63 相当于模 64 } }
编程学习
技术分享
实战经验