20250312NOI模拟赛
20250312NOI模拟赛
时间安排
这个没啥好写的,上来想了四十分钟上下,觉得这些差不多够自己写一场的了就开始写。中间发现自己看错题一次,但是只是很好改,写了三个半小时多的代码,比赛结束。
代码写的还是要再快点的,看错题这个东西要多注意一下。
T1: 「2022-2023 集训队互测 Round 6」>.<
题面:
给定一张 \(n\) 个点 \(m\) 条边的边带权有向简单图和 \(K\) 条长 \(len_i\) 的路径,求最短的 \(1\) 到 \(n\) 路径使得其不包含给出的 \(K\) 条路径中的任意一条。以上路径均可以不是简单路径。 \(n,m,K,L=\sum len_i\leq 2e5\)
题解:
走过的路径不包含给出的路径就可以抽象成一个字符串匹配问题,考虑 \(AC\) 自动机。
暴力想法是将每个路径插入 \(AC\) 自动机,那问题变成在原图上跑最短路时,在 \(AC\) 自动机上一起动,不能经过 \(AC\) 自动机的某些点(即每个路径的末尾和其 \(fail\) 树子树中的点)。这题的 \(trie\) 图并不是很好建,因为它儿子的字符集是 \(n\),可以用 \(map\) 等容器只建需要的点,如果我们忽略这部分复杂度,暴力最短路可以做到 \(O(mL\log mL)\)。
暴力代码(别人的)
#include<bits/stdc++.h>
#define int long long
//#define int __int128
#define ll long long
#define pa pair<int,int>
//#define int unsigned long long
#define sg signed
#define fi first
#define se second
#define ls s[x][0]
#define rs s[x][1]
#define ld long double
#define bs basic_string
const int inf=1e18;
using namespace std;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if(ch=='-')w=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
s=s*10+ch-'0',ch=getchar();
return s*w;
}
int n,m,k;
struct nod{
int x,y,w;
};
bool operator>(nod a,nod b){
return a.w>b.w;
}
priority_queue<nod,vector<nod>,greater<nod> >q;
bs<pa>v[200005];
int fl[200005],tot;
bool ed[200005];
map<int,int>dis[200005];
map<int,bool>vis[200005];
map<int,int>tr[200005];
int to(int x,int y){
if(tr[x].find(y)==tr[x].end()){
if(!x)return 0;
return tr[x][y]=to(fl[x],y);
}
return tr[x][y];
}
void build(){
queue<int>q;
for(auto a:tr[0])q.push(a.se);
while(!q.empty()){
int x=q.front();q.pop();
ed[x]|=ed[fl[x]];
for(auto a:tr[x]){
int y=a.se,w=a.fi;
fl[y]=to(fl[x],w);
q.push(y);
}
}
}
void solve(){
n=read(),m=read(),k=read();
for(int i=1;i<=m;i++){
int x=read(),y=read(),w=read();
v[x]+=pa{y,w};
}
for(int i=1;i<=k;i++){
int p=0;
int len=read();
while(len--){
int x=read();
if(!tr[p][x])tr[p][x]=++tot;
p=tr[p][x];
}
ed[p]=1;
}
build();
dis[1][to(0,1)]=0;
q.push({1,to(0,1),0});
while(!q.empty()){
int x=q.top().x,a=q.top().y;
q.pop();
if(vis[x][a])continue;
vis[x][a]=1;
// cout<<x<<' '<<a<<' '<<dis[x][a]<<" "<<ed[a]<<" "<<fl[a]<<endl;
if(ed[a])continue;
for(auto A:v[x]){
int y=A.fi,w=A.se;
int k=to(a,y);
if(dis[y].find(k)==dis[y].end()||dis[y][k]>dis[x][a]+w){
dis[y][k]=dis[x][a]+w;
q.push({y,k,dis[y][k]});
}
}
}
int ans=1e18;
for(auto a:dis[n]){
int x=a.fi,val=a.se;
if(ed[x])continue;
ans=min(ans,val);
}
if(ans==1e18)ans=-1;
printf("%lld\n",ans);
}
signed main(){
int t=1;while(t--)solve();
return 0;
}
但是我们发现,明明只有 \(O(L)\) 的点,每个点也只会被更新一次,但是枚举边的复杂度却是 \(O(mL)\),因为我们对于每个点都要枚举他所有的出边判断哪个要更新。那么我们考虑,在 \(fail\) 树上做主席树来实现 \(trie\) 图,以 \(x\) 为根的线段树中下标为 \(i\) 的点表示,从 \(x\) 这个点在 \(trie\) 图上沿 \(i\) 的方向走一步会到达哪个点。因为一个点只会被更新一次,通过维护线段树子树中还没有被更新的点的个数,可以帮助我们直接找到每一个没被更新的点,而不用遍历每一个出边,这样就用一个 \(\log\) 代替了 \(m\) 的复杂度。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=2e5+5,M=N<<1;
const ll inf=1e18;
int rt[M],n,fal[M],tot,faw[M],m,K;
ll dis[M];
bool vis[M],ed[M];
map<int,int>tr[M];
map<int,int>e[N];
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
namespace seg{
int tot;
struct Tree{
int l,r,siz,v;
}t[N*55];
void pushup(int s){
t[s].siz=t[t[s].l].siz+t[t[s].r].siz;
}
void update(int &s,int u,int l,int r,int x,int v){
s=++tot; t[s]=t[u];
if(l==r){
t[s].v=v;
return ;
}
int mid=l+r>>1;
if(x<=mid) update(t[s].l,t[u].l,l,mid,x,v);
else update(t[s].r,t[u].r,mid+1,r,x,v);
pushup(s);
}
void modify(int &s,int u,int l,int r,int x){
s=++tot; t[s]=t[u];
if(l==r){
t[s].siz=1;
return ;
}
int mid=l+r>>1;
if(x<=mid) modify(t[s].l,t[u].l,l,mid,x);
else modify(t[s].r,t[u].r,mid+1,r,x);
pushup(s);
}
int query(int s,int l,int r,int x){
if(l==r) return t[s].v;
int mid=l+r>>1;
if(x<=mid) return query(t[s].l,l,mid,x);
else return query(t[s].r,mid+1,r,x);
}
void dfs(int s,int l,int r,int x){
if(!t[s].siz) return ;
if(l==r){
t[s].siz=0;
int v=t[s].v;
if(ed[v]) return ;
if(dis[v]>dis[x]+e[faw[x]][faw[v]]){
dis[v]=dis[x]+e[faw[x]][faw[v]];
q.push({dis[v],v});
}
return ;
}
int mid=l+r>>1;
dfs(t[s].l,l,mid,x);
dfs(t[s].r,mid+1,r,x);
pushup(s);
}
}
void build(){
queue<int>q;
for(int i=1;i<=n;i++){
int x=tr[0][i];
q.push(x); rt[x]=rt[0];
for(pair<int,int> v:e[i])
seg::modify(rt[x],rt[x],1,n,v.first);
}
while(!q.empty()){
int x=q.front(); q.pop();
if(x>n) rt[x]=rt[fal[x]];
ed[x]|=ed[fal[x]];
for(auto [v,w]:tr[x]){
fal[w]=seg::query(rt[fal[x]],1,n,v);
seg::update(rt[x],rt[x],1,n,v,w);
q.push(w);
}
}
}
void dijkstra(){
memset(dis,0x3f,sizeof(dis));
q.push({0,1}); dis[1]=0;
while(!q.empty()){
int x=q.top().second;
q.pop();
if(vis[x]) continue;
vis[x]=1;
seg::dfs(rt[x],1,n,x);
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();m=read();K=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
e[u][v]=w;
}
for(int i=1;i<=n;i++){
tr[0][i]=++tot; faw[tot]=i;
seg::update(rt[0],rt[0],1,n,i,tot);
}
for(int i=1;i<=K;i++){
int x=0;
int len=read();
while(len--){
int v=read();
if(!tr[x][v]){
tr[x][v]=++tot;
faw[tot]=v;
}
x=tr[x][v];
}
ed[x]=1;
}
build();
dijkstra();
ll ans=inf;
for(int i=1;i<=tot;i++)
if(faw[i]==n) ans=min(ans,dis[i]);
if(ans>inf/2) puts("-1");
else printf("%lld\n",ans);
return 0;
}
T2:「2022-2023 集训队互测 Round 5」在路上
题面:
交互题
有一棵 \(n\) 个点树,保证点数是奇数,让你求重心,\(T\) 组数据中使用不超过 \(m\) 次操作。
每次你可以询问三个点 \(ask(x,y,z)\) 如果存在一条简单路径同时经过这三个点,返回中间的那个点,否则返回 \(0\)。
\(subtask1:T=100,n=3,m=100\)
\(subtask2:T=100,n=49999,m=2.5e7\) 树是一条链。
\(subtask3:T=100,n=29999,m=5e7\)
题解:
\(subtask1:\) 直接 \(return\ \ ask(1,2,3);\)
\(subtask2:\) 要找到一个序列最重心的点,考虑类 \(nth\_element\) 的实现。那么我们需要找到链的两个端点,以及如何判断 \(x,y\) 所在的位置 \(p_x,p_y\) 谁更小。先看第一步,设当前暂时的端点是 \(l,r\),每加入一个新点 \(i\),询问 \(v=ask(l,i,r)\),不等于 \(v\) 的两个点就是新的左右端点,依次加入每个点更新一遍就可以了。期望次数 \(O(n)\)。
\(subtask3:\) 考虑随机一条链,然后按 \(subtask2\) 做。因为重心的每个子树大小 \(\leq \frac n 2\),所以随机一条链 \(l\to r\),重心在链上的概率不小于 \(\frac 1 2\),期望 \(O(1)\) 次找到一个包含重心的链,然后套用 \(subtask2\) 的做法。但是会多有几个问题,一是怎么判断一个点到底是不是重心,二是因为这条链上的点旁边还挂了别的子树,导致其每个点的子树大小不一,重心也不在中间,直接 \(nth\_element\) 的复杂度不对。
先解决第一个问题,如果 \(rt\) 不是重心,那么其必然有一棵子树大小 \(>\frac n 2\),可以做摩尔投票来解决。
那么我们就需要判断连个点是否不在以 \(rt\) 为根的同一个子树中,显然判断方式是 \(ask(x,y,rt)==rt\)。
再解决第二个问题,既然每个点子树大小不一,就把子树大小当做权重,越“重”的随机到的概率越大。
这个操作可以分成两步,先从链和他们的子树全部的点中随机一个,再找到他在链上的点是哪个,这个随机显然符合权重。那么现在的问题就只剩如何找到 \(v\) 在链上对应的点。设暂时的答案是 \(u\),遍历链上每一个点 \(i\),如果 \(ask(u,i,v)==i\),令 \(u\leftarrow i\),\(u\) 即为所求。
总期望次数 \(O(n)\),直接写基本是能过的,更多常数分析及优化详见官方题解。
代码
#include<bits/stdc++.h>
#include "path.h"
#define ll long long
using namespace std;
mt19937 rnd(time(0));
int n;
int check(int rt,vector<int>vec){
if(vec.size()<=n/2) return rt;
int cnt=0,col=0;
for(int x:vec){
if(cnt==0) cnt++,col=x;
else{
if(ask(rt,x,col)==rt) --cnt;
else cnt++;
}
}
if(cnt==0) return rt;
cnt=0;
for(int x:vec)
if(ask(rt,x,col)!=rt) cnt++;
return cnt<=n/2?rt:-1;
}
vector<int> operator + (vector<int> x,vector<int> y){
x.insert(x.end(),y.begin(),y.end());
return x;
}
int solve(int l,int r,vector<int>L,vector<int>R,vector<int>M,vector<int>D){
if(L.size()>n/2) return check(l,L);
if(R.size()>n/2) return check(r,R);
if(M.size()==0){
if(R.size()+1<=n/2) return l;
if(L.size()+1<=n/2) return r;
return -1;
}
if(M.size()==1){
if(L.size()+1+1+D.size()<=n/2) return r;
if(R.size()+1+1+D.size()<=n/2) return l;
if(L.size()+1<=n/2&&R.size()+1<=n/2) return check(M[0],D);
return -1;
}
int k=rnd()%(M.size()+D.size());
if(k<M.size()) k=M[k];
else{
int v=D[k-M.size()];
k=l;
for(int z:M)
if(ask(k,v,z)==z) k=z;
}
vector<int>ML,MR,SK,DL,DR;
for(int x:M)
if(x!=k){
if(ask(l,x,k)==x) ML.push_back(x);
else MR.push_back(x);
}
for(int x:D)
if(ask(l,k,x)==k){
if(ask(r,k,x)==k) SK.push_back(x);
else DR.push_back(x);
}
else DL.push_back(x);
int ls=L.size()+DL.size()+ML.size()+1,rs=R.size()+DR.size()+MR.size()+1;
if(ls<=n/2&&rs<=n/2) return check(k,SK);
if(rs>n/2) swap(L,R),swap(ML,MR),swap(DL,DR),swap(l,r);
R.push_back(r);
for(int x:SK) R.push_back(x);
for(int x:MR) R.push_back(x);
for(int x:DR) R.push_back(x);
r=k;
return solve(l,r,L,R,ML,DL);
}
int centroid(int id,int n,int m){
::n=n;
if(id==1) return ask(1,2,3);
if(id==3||id==5){
int l=1,r=2;
for(int i=3;i<=n;i++){
int v=ask(l,i,r);
if(l==v) l=i;
if(r==v) r=i;
}
vector<int>vec;
for(int i=1;i<=n;i++) vec.push_back(i);
nth_element(vec.begin(),vec.begin()+n/2,vec.end(),[&](int x,int y){return ask(x,y,r)==y;});
return vec[n/2];
}
while(1){
int l=rnd()%n+1,r=rnd()%n+1;
if(l==r) continue;
vector<int>L,R,M,D;
for(int i=1;i<=n;i++)
if(i!=l&&i!=r){
int v=ask(i,l,r);
if(v==l) L.push_back(i);
if(v==r) R.push_back(i);
if(v==i) M.push_back(i);
if(v==0) D.push_back(i);
}
int ans=solve(l,r,L,R,M,D);
if(~ans) return ans;
}
}
T3:「2022-2023 集训队互测 Round 2」相等树链
题面:
给两个 \(n\) 个点的树 \(T_1,T_2\),问有多少个点集 \(S\) 在 \(T_1,T_2\) 上均为一条链。
一个点集 \(S\) 在一棵树 \(T\) 上是一条链当且仅当存在 \(1\leq x\leq y\leq n\),\(T\) 上 \(x\to y\) 的路径经过的点集恰好是 \(S\)。\(n\leq 2e5\)
题解:
一看到多棵树一起统计什么东西很套路的想到点分治,对 \(T_1\) 进行点分治,考虑如何统计在 \(T_1\) 上经过当前重心 \(rt\) 且长度大于等于 \(2\) 的合法路径 \((x,y)\) 的数量(因为长度为 \(1\) 的路径一定合法且正好是 \(n\) 个)。
记 \(P_i(x,y)\) 为 \(T_i\) 上 \(x\to y\) 的点集,\(A(x,y)=P_1(x,y)\backslash\{y\}\),\(B(x,y)=P_2(x,y)\backslash\{y\}\)。
设一个合法的点集在 \(T_1\) 上的路径两端点是 \(x,y\),在 \(T_2\) 上是 \(z,w\)。
对于 \(A(x,rt)\) 设 \(0\leq k\leq 2,a_{1\sim k}\),表示这个集合在 \(z\to w\) 上 \(rt\) 两边距 \(rt\) 最远的点是哪个。
若 \(A(x,rt)=\emptyset\),\(k=0\)。 若 \(A(x,rt)\) 的点全都在 \(rt\) 的一边,\(k=1\)。 否则 \(k=2\)。
对 \(A(y,rt)\) 同样有 \(0\leq l\leq 2,b_{1\sim l}\)。
如果是一条从 \(rt\) 只向一个方向延伸的路径,不妨设 \(A(y,rt)=\emptyset,z=rt\),此时 \(k=1,a_1=w\),有 \(A(x,rt)=B(a_1,rt)\)。
对于从 \(rt\) 向两个方向延伸的路径:
若 \(z,w\in A(x,rt)\),有 \(k=2,\{a_1,a_2\}=\{z,w\}\),则 \(A(x,rt)\oplus A(y,rt)=B(a_1,rt)\oplus B(a_2,rt)\)。
同理若 \(z,w\in A(y,rt)\),有 \(A(x,rt)\oplus A(y,rt)=B(b_1,rt)\oplus B(b_2,rt)\)。
若 \(z\in A(x,rt),w\in A(y,rt)\) 亦或是反过来,存在唯一的 \(1\leq i\leq k,1\leq j\leq l\) 使得 \(a_i=z,b_j=w\),则 \(A(x,rt)\oplus A(y,rt)=B(a_i,rt)\oplus B(b_j,rt)\)。
对上述三个式子移项得:
\(A(x,rt)\oplus B(a_1,rt)\oplus B(a_2,rt)=A(y,rt)\)
\(A(x,rt)=B(b_1,rt)\oplus B(b_2,rt)\oplus A(y,rt)\)
\(A(x,rt)\oplus B(a_i,rt)=B(b_j,rt)\oplus A(y,rt)\)
这样我们把判定变成了只和 \(x,y\) 一方有关的两个值,可以给每个点随机一个值做异或哈希统计答案。
但是第三个式子不是充要条件,这样会多算方案。
第三种情况的方案合法的充要条件是 \(a_i\) 和 \(b_j\) 不在同一侧且满足上式,那么我们如上统计后要减去满足上式且 \(a_i,b_j\) 在同一侧的方案。在计算 \(a_i,b_j\) 是可以同时对点 \(x\) 维护 \(rt\to x\) 路径上的第二个点 \(c(x)\),\(a_i,b_j\) 在同一侧即 \(c(a_i)=c(b_j)\)。
如何计算 $a_i,b_j,c(x)$
设当前分治重心是 \(rt\)。当前要算的点集是 \(S\),把 \(S\) 中的点全部打标记。在 \(T_2\) 上从 \(rt\) 开始只走打标记的点向外扩展,可以直接得到 \(c(x)\) 和到 \(rt\) 的距离(也就是深度) \(dep_x\)。
对于 \(T_1\) 上 \(S\) 中的点 \(x\),\(A(x,rt)\) 的 \(k\) 和 \(a_i\) 先假定为 \(A(fa_x,rt)\) 的答案,然后新加一个点 \(x\),根据 \(c(x)\) 可以判断在不在 \(T_2\) 中 \(rt\) 的同一侧,根据 \(dep_x\) 更新距离 \(rt\) 最远的点是哪个。
namespace B{
void dfs(int x,int fa,int id){
dep[x]=dep[fa]+1;
exi[x]=1; C[x]=id;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||!nok[v]) continue;
dfs(v,x,id);
}
}
void sol(int x){
dep[x]=1;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(nok[v]) dfs(v,x,v);
}
}
}
namespace A{
void dfz(int x,int fa){
nok[x]=1;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||vis[v]) continue;
dfz(v,x);
}
}
int cmax(int x,int y){
if(dep[x]>dep[y]) return x;
else return y;
}
void dfs(int x,int fa,int A,int B){
if(!exi[x]) return ;
if(A&&B){
if(C[x]!=C[A]&&C[x]!=C[B]) return ;
if(C[x]==C[A]) A=cmax(A,x);
if(C[x]==C[B]) B=cmax(B,x);
}
else if(A){
if(C[x]==C[A]) A=cmax(A,x);
else B=x;
}
else A=x;
a[x]={A,B};
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||vis[v]) continue;
dfs(v,x,A,B);
}
}
void sol(int x){
dfz(x,0);
B::sol(x);
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(vis[v]) continue;
dfs(v,x,0,0);
}
}
}
A::sol(rt);
代码
#include<bits/stdc++.h>
#define ll long long
#define ull unordered_map<ll,int>
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
mt19937_64 rnd(time(0));
const int N=2e5+5;
int n,dep[N],col[N],C[N];
ll w[N],val[N],ans;
bool nok[N],exi[N];
namespace B{
int head[N],cnt;
struct edge{
int v,nxt;
}e[N<<1];
void add(int u,int v){
e[++cnt].v=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int x,int fa,int c,int id){
val[x]=val[fa]^w[x];
dep[x]=dep[fa]+1;
col[x]=c; exi[x]=1; C[x]=id;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||!nok[v]) continue;
dfs(v,x,c,id);
}
}
void del(int x,int fa){
exi[x]=0;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||!nok[v]) continue;
del(v,x);
}
}
void clear(int x){
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(nok[v]) del(v,x);
}
}
void sol(int x){
dep[x]=1; val[x]=0;
int tot=0;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(nok[v]) dfs(v,x,++tot,v);
}
}
}
namespace A{
int head[N],cnt,tot,rt,mx,siz[N];
pair<int,int>a[N];
unordered_map<ll,int>X,Y,Z[N];
ll dis[N];
bool vis[N],rev[N];
struct edge{
int v,nxt;
}e[N<<1];
void add(int u,int v){
e[++cnt].v=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfz(int x,int fa){
siz[x]=1;nok[x]=1;
int num=0;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||vis[v]) continue;
dfz(v,x);
siz[x]+=siz[v];
num=max(num,siz[v]);
}
num=max(num,tot-siz[x]);
if(num<mx){
mx=num;
rt=x;
}
}
int cmax(int x,int y){
if(dep[x]>dep[y]) return x;
else return y;
}
ll Xor(int x){
ll ans=dis[x];
if(a[x].first) ans^=val[a[x].first];
if(a[x].second) ans^=val[a[x].second];
return ans;
}
void dfs(int x,int fa,int A,int B){
if(!exi[x]) return ;
if(A&&B){
if(col[x]!=col[A]&&col[x]!=col[B]) return ;
if(col[x]==col[A]) A=cmax(A,x);
if(col[x]==col[B]) B=cmax(B,x);
}
else if(A){
if(col[x]==col[A]) A=cmax(A,x);
else B=x;
}
else A=x;
rev[x]=1;
a[x]={A,B};
dis[x]=dis[fa]^w[x];
ans+=X[Xor(x)];
ans+=Y[dis[x]];
if(A){
ans+=Z[0][dis[x]^val[A]];
ans-=Z[C[A]][dis[x]^val[A]];
}
if(B){
ans+=Z[0][dis[x]^val[B]];
ans-=Z[C[B]][dis[x]^val[B]];
}
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||vis[v]) continue;
dfs(v,x,A,B);
}
}
void calc(int x,int fa){
if(!rev[x]) return ;
X[dis[x]]++;
Y[Xor(x)]++;
int A,B;
tie(A,B)=a[x];
if(A){
Z[0][dis[x]^val[A]]++;
Z[C[A]][dis[x]^val[A]]++;
}
if(B){
Z[0][dis[x]^val[B]]++;
Z[C[B]][dis[x]^val[B]]++;
}
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||vis[v]) continue;
calc(v,x);
}
}
void del(int x,int fa){
if(!rev[x]) return ;
rev[x]=0;
int A,B;
tie(A,B)=a[x];
if(A) ull().swap(Z[C[A]]);
if(B) ull().swap(Z[C[B]]);
a[x]={0,0};
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||vis[v]) continue;
del(v,x);
}
}
void getsz(int x,int fa){
siz[x]=1;nok[x]=0;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa||vis[v]) continue;
getsz(v,x);
siz[x]+=siz[v];
}
}
void sol(int x){
vis[x]=1;
B::sol(x);
dis[x]=0; X[0]++;
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(vis[v]) continue;
dfs(v,x,0,0);
calc(v,x);
}
B::clear(x);
ull().swap(X);ull().swap(Y);ull().swap(Z[0]);
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(vis[v]) continue;
del(v,x);
}
getsz(x,0);
for(int i=head[x],v;i;i=e[i].nxt){
v=e[i].v;
if(vis[v]) continue;
tot=siz[v];mx=n+1,rt=0;
dfz(v,x); sol(rt);
}
}
void solve(){
rt=0;mx=n+1,tot=n;
dfz(1,0); sol(rt);
printf("%lld\n",ans+n);
}
}
int main(){
// freopen("line5.in","r",stdin);
// freopen(".out","w",stdout);
n=read();
for(int i=2;i<=n;i++){
int x=read();
A::add(x,i);A::add(i,x);
}
for(int i=2;i<=n;i++){
int x=read();
B::add(x,i);B::add(i,x);
}
for(int i=1;i<=n;i++) w[i]=rnd();
// exit(0);
A::solve();
return 0;
}