【题解】GXOI/GZOI2019 机房游记
教练不知道从哪里嫖来的数据(或许是洛谷或者(Loj)?),只说是让我们做套模拟题,后来发现是今年 ( ext{GX,GZ}) 两省联考的考试题。
由于时间关系,只考 (4) 个小时。本来就不会做,还不给狗时间骗分,这是要爆蛋的节奏啊。
时间:2019-1-9 (机房 day1)
考前几分钟打了一局PvZ。
(T1) 貌似写的是正解,(T2) 直接放弃了,如果再多一个小时或许能骗一点分?(T3) 有一个部分用了我最爱的 ( ext{CDQ}),而且刚好就是我前几天出的那道题的类型,愉快地码了接近两个小时,希望不要写挂吧。
期望得分:(100+0+100) 。
实际得分:(100+0+30) 。
等等,(T3) 那些在 (caiji) 评测机 (Cena) 上 (WA) 掉的数据在本地运行可以过,于是又换了台机子重测。
真·实际得分 :(100+0+100) 。
时间:2019-1-9 (机房 day2)
(T1) 研究了 (2) 个小时后推出式子,十几分钟码出来开拍。
(T2) 直接暴力,(T3) 数据结构题,敲了一个多小时后感觉 (50pt) 稳了,又回去看 (T2),发现可以用 (SPFA) 跑多源次短路骗分,(10min) 敲好开溜。
期望得分:(100+(40 sim 100)+50) 。
实际得分:(100+40+50) 。
今天把PvZ通关了,开始硬刚95版和beta版。
【题解】
【Day1 T1】
上来先开 ( ext{T1}) 码暴力。一开始把 (or) 看成了 (xor),想着直接上矩阵前缀和,而且居然还过了第一个样例,对着样例 (2) 手玩了好久才发现是应该是 (or)。
我太菜了。
回想起以前做过的一道题:( ext{bob [COCI2015]}),如果所有数字都是 (0) 或者 (1) 的话,就可以直接上单调栈扫过去,很明显这道题可以尝试一些转换。
由于 (and) 和 (or) 运算对于二进制数字的每一位都是独立的,可以把所有数都拆成 (31) 位 (0) 或 (1),构造出 (31) 个新的矩阵。
对于每一个矩阵的求解:注意到 (and) 实际上就是统计有多少个全为 (1) 的子矩阵,而 (or) 则是总的子矩阵个数 (left(frac{n(n+1)}{2} ight)^2) 减去全为 (0) 的子矩阵个数。直接单调栈一遍扫过去。
时间复杂度:(O(n^2loginf)) 。
【Code】
#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1000+3,P=1e9+7;
int n,t,tot,Ans1,Ans2,Q[N],W[N],a[N][N],A[N][N],U[N][N],LU[N][N];
inline void in(Re &x){
Re fu=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=fu?-x:x;
}
inline void sakura(){
for(Re j=1;j<=n;++j)
for(Re i=1;i<=n;++i)
if(a[i-1][j]!=a[i][j])U[i][j]=1;
else U[i][j]=U[i-1][j]+1;
for(Re i=1;i<=n;++i)
for(Re j=1;j<=n;++j)
if(a[i][j]!=a[i][j-1])t=0,Q[++t]=j,W[t]=1,LU[i][j]=U[i][j];
else{
Re len=1;LU[i][j]=U[i][j];
while(t&&U[i][Q[t]]>U[i][j])(LU[i][j]+=(LL)W[t]*U[i][j]%P)%=P,len+=W[t--];
if(t)(LU[i][j]+=LU[i][Q[t]])%=P;
Q[++t]=j,W[t]=len;
}
}
int main(){
// freopen("andorsum.in","r",stdin);
// freopen("andorsum.out","w",stdout);
in(n),tot=(LL)n*n*(n+1)*(n+1)/4%P;
for(Re i=1;i<=n;++i)
for(Re j=1;j<=n;++j)
in(A[i][j]);
memset(a,-1,sizeof(a));
for(Re k=30;k>=0;--k){
Re flag=0,p=(1<<k)%P;
for(Re i=1;i<=n;++i)
for(Re j=1;j<=n;++j)
flag|=(a[i][j]=((A[i][j]>>k)&1));
if(!flag)continue;
Re tmp1=0,tmp2=0;
sakura();
for(Re i=1;i<=n;++i)
for(Re j=1;j<=n;++j)
if(a[i][j])(tmp1+=LU[i][j])%=P;
else (tmp2+=LU[i][j])%=P;
(Ans1+=(LL)tmp1*p%P)%=P,(Ans2+=(tot-tmp2+P)%P*1ll*p%P)%=P;
}
printf("%d %d
",Ans1,Ans2);
fclose(stdin);
fclose(stdout);
}
【Day1 T2】
神奇的 (dp),不会,先咕着(实际上永远都不会补上了)。
【Day1 T3】
一道大膜您。
((1).) 首先是求交点。
设 (A[i]) 为飞机 (i) 需要到达的点的排名。
求交点的本质就是在求:对于所有满足(A[i]<A[j])(写法上不同可能是 (A[i]>A[j]))的 (i,j) ((i<j)),直线 (i) 必定与直线 (j) 相交。“统计对于所有满足 (XXX) 条件的 (i,j) ((i<j))”,这不就是个 ( ext{CDQ}) 的板子操作吗?由于交点数不大于 (5e5),可以直接在递归中暴力枚举求交,时间复杂度为:(O(nlogn+) ( ext{交点数}) ()) 。
((2).) 计算被观测到的交点的数量。
先把所有的点都顺时针旋转 (45^circ),然后做二维数点,可以用离线排序+扫描线+树状数组解决。
(注意 是否被观测 与 选择哪种特技表演 其实是相互独立的,两者可以分开处理)
((3).) 分别计算两种特技表演的次数。
每交换一次就相当于是把某个 (A[i],A[j]) 的值进行了交换,而最后的 (A) 序列一定是排好序的,是不是和求交点很像?模拟一下会发现:在所有的交点处都进行一次交换,最后的达到高度必定满足排名要求。因此当 (a>b) 时最大得分一定为 (a) (*) ( ext{交点个数}) 。
下面考虑 (a<b) 的情况:用尽量少的交换次数满足到达高度的排名。将所有的 (i) 与 (A[i]) 连边,最后一定会形成若干个环,每个大小为 (x) 的环都可以用 (x-1) 次交换(环中的点相互一共有 (x-1) 个交点)将其归位,而对于不同环中的点的交点则不用管,答案为:(n-) ( ext{环的个数}) 。可以发现这样算出来即为最少需要的交换次数。
时间复杂度: (O(nlogn)) 。
#include<algorithm>
#include<cstdio>
#include<cmath>
#define LL long long
#define LD double
#define Re register int
#define Vector Point
using namespace std;
const int N=1e5+3,M=5e5+3;
const LD eps=1e-8,Pi=acos(-1);
int n,a,b,c,K,st,ed,cnt,H1[N],H2[N];LL Ans1,Ans2;
inline int dcmp(LD a){return a<-eps?-1:(a>eps?1:0);}
inline LD Abs(LD a){return dcmp(a)*a;}
inline void in(Re &x){
Re fu=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=fu?-x:x;
}
struct Point{
LD x,y;Point(LD X=0,LD Y=0){x=X,y=Y;}
inline void in(){scanf("%lf%lf",&x,&y);}
inline void out(){printf("%.2lf %.2lf
",x,y);}
}CR[M];
struct Line{Point a,b;}Li[N];
inline LD Dot(Vector a,Vector b){return a.x*b.x+a.y*b.y;}//【点积】
inline LD Cro(Vector a,Vector b){return a.x*b.y-a.y*b.x;}//【叉积】
inline LD Len(Vector a){return sqrt(Dot(a,a));}//【模长】
inline Vector operator+(Vector a,Vector b){return Vector(a.x+b.x,a.y+b.y);}
inline Vector operator-(Vector a,Vector b){return Vector(a.x-b.x,a.y-b.y);}
inline Vector operator*(Vector a,LD b){return Vector(a.x*b,a.y*b);}
inline bool operator==(Point a,Point b){return !dcmp(a.x-b.x)&&!dcmp(a.y-b.y);}//两点坐标重合则相等
inline Point turn_P(Point a,LD theta){//【点A向量A顺时针旋转theta(弧度)】
LD x=a.x*cos(theta)+a.y*sin(theta);
LD y=-a.x*sin(theta)+a.y*cos(theta);
return Point(x,y);
}
inline Point cross_LL(Line v1,Line v2){//【两直线AB,CD的交点】
Point a=v1.a,b=v1.b,c=v2.a,d=v2.b;
Vector x=b-a,y=d-c,z=a-c;
return a+x*(Cro(y,z)/Cro(x,y));//点A加上向量AF
}
struct Sakura1{
int A[N],B[N];
struct QWQ{int i,A;QWQ(int I=0,int a=0){i=I,A=a;}}A_[N],A__[N];
inline void merge(QWQ *P,Re p1,Re t1,Re p2,Re t2){//归并
Re t=p1-1;
while((p1<=t1||p2<=t2))
if((p1<=t1&&A_[p1].A>A_[p2].A)||p2>t2)P[++t]=A_[p1++];//注意判断是否大于t1,t2
else P[++t]=A_[p2++];
}
inline void CDQ(Re L,Re R){
if(L==R)return;
Re mid=L+R>>1,p1=mid,p2=R+1;
CDQ(L,mid),CDQ(mid+1,R);
while(p1>=L){
while(p2>mid+1&&A_[p1].A>A_[p2-1].A)--p2;
for(Re k=p2;k<=R;++k)CR[++cnt]=cross_LL(Li[A_[p1].i],Li[A_[k].i]);//这里的暴力只会枚举[交点个数]次
--p1;
}
merge(A__,L,mid,mid+1,R);
for(Re i=L;i<=R;++i)A_[i]=A__[i];
}
inline void sakura(){
for(Re i=1;i<=n;++i)B[i]=H2[i];
sort(B+1,B+n+1);
for(Re i=1;i<=n;++i)A[i]=lower_bound(B+1,B+n+1,H2[i])-B;
for(Re i=1;i<=n;++i)A_[i]=QWQ(i,A[i]);
CDQ(1,n);
}
}T1;
struct View{Point O;LD r;}V[N];
struct Sakura2{
#define lo(a) (lower_bound(b+1,b+m+1,a)-b)
LD b[M+(N<<1)];
struct BIT{
int n,C[M+(N<<2)];
inline void add_(Re x,Re v){while(x<=n)C[x]+=v,x+=x&-x;}
inline void add(Re L,Re R,Re v){add_(L,v),add_(R+1,-v);}
inline int ask(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}
}TR;
struct Point_{LD x;int y;inline bool operator<(const Point_ &O)const{return dcmp(x-O.x)<0;}}Q[M];
struct Line_{LD x;int y1,y2,k;inline bool operator<(const Line_ &O)const{return dcmp(x-O.x)<0;}}L[N<<1];
int m,t;
inline void sakura(){
Re tmp=0;LD sq2=sqrt(2.0);
for(Re i=1;i<=cnt;++i)CR[i]=turn_P(CR[i],Pi/4.0),b[++m]=CR[i].y;//旋转所有点
for(Re i=1;i<=K;++i)
V[i].O=turn_P(V[i].O,Pi/4.0),V[i].r*=sq2,
b[++m]=V[i].O.y-V[i].r/2.0,b[++m]=V[i].O.y+V[i].r/2.0;//离散化所有需要用到的纵坐标
sort(b+1,b+m+1),TR.n=m=unique(b+1,b+m+1)-b-1;
for(Re i=1;i<=cnt;++i)Q[i].x=CR[i].x,Q[i].y=lo(CR[i].y);
for(Re i=1;i<=K;++i)
L[++t]=(Line_){V[i].O.x-V[i].r/2.0,lo(V[i].O.y-V[i].r/2.0),lo(V[i].O.y+V[i].r/2.0),1},
L[++t]=(Line_){V[i].O.x+V[i].r/2.0+eps*2.0,lo(V[i].O.y-V[i].r/2.0),lo(V[i].O.y+V[i].r/2.0),-1};//注意在边界上也算,所以要把右边界向右移一点
sort(Q+1,Q+cnt+1),sort(L+1,L+t+1);
Re now=0;
for(Re i=1;i<=cnt;++i){
while(now<t&&dcmp(L[now+1].x-Q[i].x)<=0)++now,TR.add(L[now].y1,L[now].y2,L[now].k);
if(TR.ask(Q[i].y))++tmp;//如果被包含
}
Ans1+=(LL)c*tmp,Ans2+=(LL)c*tmp;
}
}T2;
struct Sakura3{
int tmp,fa[N];
inline int find(Re x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void merge(Re x,Re y){
if((x=find(x))!=(y=find(y)))fa[x]=y;
else --tmp;//出现了一个环
}
inline void sakura(){
tmp=n;
for(Re i=1;i<=n;++i)fa[i]=i;
for(Re i=1;i<=n;++i)merge(i,T1.A[i]);
Ans1+=tmp*a+(cnt-tmp)*b,Ans2+=cnt*a;
}
}T3;
int main(){
// freopen("aerobatics.in","r",stdin);
// freopen("aerobatics.out","w",stdout);
in(n),in(a),in(b),in(c),in(st),in(ed);
for(Re i=1;i<=n;++i)in(H1[i]),Li[i].a=Point(st,H1[i]);
for(Re i=1;i<=n;++i)in(H2[i]),Li[i].b=Point(ed,H2[i]);
in(K);for(Re i=1;i<=K;++i)V[i].O.in(),scanf("%lf",&V[i].r);
T1.sakura();//获取n条直线的所有交点CR
T2.sakura();//获取被观测到的交点数量
T3.sakura();//获取两种特技的表演次数
if(a<b)swap(Ans1,Ans2);
printf("%lld %lld
",Ans1,Ans2);
fclose(stdin);
fclose(stdout);
}
【Day2 T1】
很明显的矩阵优化 (dp) 转移,问题是 (dp) 方程要如何求得。
设 (f[n]) 表示放了 (n) 个正常方块的方案数,很明显是个斐波那契数列:(f[n]=f[n-1]+f[n-2]),其中 (f[n-1]) 的含义是新加了一块竖着的方块,(f[n-2]) 是新加了两块横着的方块。
设 (dp[n]) 表示放了 (n) 个方块的答案,对于新增的正常方块一样有 (dp[n]=dp[n-1]+dp[n-2]),现在考虑特殊方块的放置。
注意到一个性质:两个特殊方块之间的所有正常方块形态只有一种,也就是说一旦确立了特殊方块的位置 (l,r) 后,(l) 到 (r) 这一段中间的方案数为 (1),左边的应为 (f[l-1]) 。
在转移 (dp[n]) 的时候,如果选择在 (n) 的最右边放一个特殊方块,那么左边的特殊方块可以在 (1,2,3...i-2) 这些位置,方案数为 (f[0]+f[1]...f[n-3]),另外,特殊方块可以上下颠倒位置,所以 (dp[n]=dp[n-1]+dp[n-2]+2sum_{i=1}^{n-3}f(i)),又因为 (sum_{i=1}^{n}Fib(i)=Fib(n+2)),所以 (dp[n]=dp[n-1]+dp[n-2]+2*f(n-1)-2) 。
最后随便设计一个矩阵即可。
时间复杂度:(O(k^3logn)),其中 (k=5) 为矩阵大小。
#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1e6+3,P=1e9+7;
int n,T;
inline void in(Re &x){
Re fu=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=fu?-x:x;
}
struct Matrix{
int a[6][6];
Matrix(){memset(a,0,sizeof(a));}
inline Matrix operator*(const Matrix &O)const{
Matrix ans;
for(Re i=1;i<=5;++i)
for(Re j=1;j<=5;++j)
for(Re k=1;k<=5;++k)
(ans.a[i][j]+=(LL)a[i][k]*O.a[k][j]%P)%=P;
return ans;
}
inline Matrix operator*=(Matrix O){return *this=*this*O;}
}A,B;
inline Matrix mi(Matrix x,Re k){
Matrix s=x;--k;
while(k){
if(k&1)s*=x;
x*=x,k>>=1;
}
return s;
}
int main(){
//freopen("obsession.in","r",stdin);
//freopen("obsession.out","w",stdout);
A.a[1][1]=0,A.a[1][2]=0,A.a[1][3]=2,A.a[1][4]=1,A.a[1][5]=-2+P;
B.a[1][1]=B.a[1][2]=B.a[2][1]=B.a[3][3]=B.a[3][4]=B.a[4][3]=B.a[5][1]=B.a[5][5]=1,B.a[3][1]=2;
in(T);
while(T--)in(n),printf("%d
",n<3?0:(A*mi(B,n-2)).a[1][1]);
fclose(stdin);
fclose(stdout);
}
【Day2 T2】
考虑染色法多源最短路。
分别在原图和反图上对关键点跑一遍多源最短路并染上色,枚举原图中的所有边,如果两端点颜色不同,则说明该边一定为某两个关键点之间的路径上,且对于该边来说,它所连的两个关键点一定是最近的,所以不断取最小值即为答案。
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register int
using namespace std;
const LL N=1e5+3,M=5e5+3,inf=1e18;
int n,m,x,y,z,K,T,X[M],Y[M],Z[M],A[N];LL Ans;
inline void in(Re &x){
Re fu=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=fu?-x:x;
}
struct Sakura{
struct QWQ{int x;LL w;QWQ(int X=0,LL W=0){x=X,w=W;}inline bool operator<(const QWQ &O)const{return w>O.w;};};
int o,head[N],pan[N],col[N];LL dis[N];
struct QAQ{int w,to,next;}a[M];priority_queue<QWQ>Q;
inline void add(Re x,Re y,Re z){if(x==y)return;a[++o].w=z,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline void dijkstra(){
for(Re i=1;i<=n;++i)dis[i]=inf,pan[i]=col[i]=0;
for(Re i=1;i<=K;++i)col[A[i]]=A[i],Q.push(QWQ(A[i],dis[A[i]]=0));
while(!Q.empty()){
Re x=Q.top().x;Q.pop();
if(pan[x])continue;
pan[x]=1;
for(Re i=head[x],to;i;i=a[i].next)
if(dis[to=a[i].to]>dis[x]+a[i].w)
col[to]=col[x],Q.push(QWQ(to,dis[to]=dis[x]+a[i].w));
}
}
}T1,T2;
inline void CL(){
for(Re i=1;i<=n;++i)T1.head[i]=T2.head[i]=0;T1.o=T2.o=0;
}
int main(){
//freopen("tourist.in","r",stdin);
//freopen("tourist.out","w",stdout);
in(T);
while(T--){
in(n),in(m),in(K),Ans=inf;
for(Re i=1;i<=m;++i)in(x),in(y),in(z),T1.add(X[i]=x,Y[i]=y,Z[i]=z),T2.add(y,x,z);
for(Re i=1;i<=K;++i)in(A[i]);
T1.dijkstra(),T2.dijkstra();
for(Re i=1;i<=m;++i){
x=X[i],y=Y[i],z=Z[i];
if(T1.col[x]&&T2.col[y]&&T1.col[x]!=T2.col[y])
Ans=min(Ans,T1.dis[x]+T2.dis[y]+z);
}
printf("%lld
",Ans),CL();
}
fclose(stdin);
fclose(stdout);
}
【Day2 T3】
考虑先将询问离线,按 (x) 的大小排序。
首先看 (k=1) 的情况,(dep[x]^k) 相当于就是统计根到 (x) 的距离加 (1),即 (dep[x]=1+1+...+1) (() 一共 (dep[x]) 个 (1) ())。当有多个 (dep[x]^k) 需要统计时,对于每个 (x),把从根到 (x) 的这条路径上的权值全部加一,然后查询 (y) 时就直接查询从根到 (y) 这条路径上的权值和。上述操作可以用一个简化版的树剖实现。
对于 (k!=1) 的情况:考虑将 (dep[x]^k) 分为 (dep[x]) 段相加,然后用类似上面的操作实现。如何分?设 (f(x)=dep[x]^k),则有 (f(x)=((dep[x]!-!1)^k)+(dep[x]^k-(dep[x]!-!1)^k)) (=f(x!-!1)+(dep[x]^k-(dep[x]!-!1)^k)),所以 (f(x)=(dep[1]^k-(dep[1]!-!1)^k) + (dep[2]^k-(dep[2]!-!1)^k)...(dep[x]-(dep[x]-1)^k)),是不是和上面的很像?只是这里每次就不是加 (1) 了,而是对于每一个在根到 (x) 路径上的点 (p) 都加上一个 ((dep[p]^k-(dep[p]!-!1)^k)) 。
但这样做复杂度会高至 (n^2),考虑用万能的线段树来维护。
由于每次加的值 ((dep[p]^k-(dep[p]!-!1)^k)) 对于 (p) 来说都是不变的,那么对于一个等待着加值的区间也是不变,所以直接预处理出线段树上所有区间的加值和,用懒标记表示该区间待加的次数,(pushdown) 的时候算次数乘以区间加值和即可。
时间复杂度:(O(nlog^2n)) 。
【Code】
#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=5e4+3,P=998244353;
int o,n,x,y,T,K,flag,A[N],AA[N],fa[N],deep[N],head[N],Ans[N];
struct QAQ{int to,next;}a[N];
inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
struct Query{int x,y,id;}Q[N];
inline bool operator<(Query A,Query B){return A.x<B.x;}
inline void in(Re &x){
Re fu=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=fu?-x:x;
}
inline int mi(Re x,Re k){
Re s=1;
while(k){
if(k&1)s=(LL)s*x%P;
x=(LL)x*x%P,k>>=1;
}
return s;
}
struct Killed_Tree{
int id_o,id[N],idx[N],top[N],son[N],size[N];
struct Segment_Tree{
#define pl (p<<1)
#define pr (p<<1|1)
#define mid (L+R>>1)
struct QAQ{int S,l,r,add,key;}tr[N<<2];
inline void updata(Re p,Re v){
(tr[p].S+=(LL)tr[p].key*v%P)%=P,(tr[p].add+=v)%=P;
}
inline void pushdown(Re p){
Re v=tr[p].add;
if(v)updata(pl,v),updata(pr,v),tr[p].add=0;
}
inline void build(Re p,Re L,Re R){
tr[p].l=L,tr[p].r=R;
if(L==R){tr[p].key=AA[L];return;}
build(pl,L,mid),build(pr,mid+1,R);
tr[p].key=(tr[pl].key+tr[pr].key)%P;
}
inline void change(Re p,Re l,Re r,Re v){
Re L=tr[p].l,R=tr[p].r;
if(l<=L&&R<=r){updata(p,v);return;}
pushdown(p);
if(l<=mid)change(pl,l,r,v);
if(r>mid)change(pr,l,r,v);
tr[p].S=(tr[pl].S+tr[pr].S)%P;
}
inline int ask(Re p,Re l,Re r){
Re L=tr[p].l,R=tr[p].r;
if(l<=L&&R<=r)return tr[p].S;
Re ans=0;pushdown(p);
if(l<=mid)(ans+=ask(pl,l,r))%=P;
if(r>mid)(ans+=ask(pr,l,r))%=P;
return ans;
}
}TR;
inline void dfs1(Re x,Re Fa){
size[x]=1,deep[x]=deep[fa[x]=Fa]+1;
for(Re i=head[x],to;i;i=a[i].next)
if((to=a[i].to)!=Fa){
dfs1(to,x);
size[x]+=size[to];
if(size[to]>size[son[x]])son[x]=to;
}
}
inline void dfs2(Re x,Re rt){
AA[id[x]=++id_o]=A[x],idx[id_o]=x,top[x]=rt;
if(!son[x])return;
dfs2(son[x],rt);
for(Re i=head[x],to;i;i=a[i].next)
if((to=a[i].to)!=fa[x]&&to!=son[x])dfs2(to,to);
}
inline void kill(Re rt){
dfs1(rt,0);
for(Re i=1;i<=n;++i)A[i]=(mi(deep[i],K)-mi(deep[i]-1,K)+P)%P;
dfs2(rt,rt),TR.build(1,1,n);
}
inline int ask_dis(Re x){//询问x到y的简单路径上权值之和
int ans=0;
while(x)(ans+=TR.ask(1,id[top[x]],id[x]))%=P,x=fa[top[x]];
return ans;
}
inline void change_dis(Re x,Re v){//x到y的简单路径上权值加上v
while(x)TR.change(1,id[top[x]],id[x],v),x=fa[top[x]];
}
}T1;
inline void sakura(){
Re now=0;
for(Re i=1;i<=T;++i){
while(now<Q[i].x)++now,T1.change_dis(now,1);
Ans[Q[i].id]=T1.ask_dis(Q[i].y);
}
}
int main(){
int size = 8 << 20;
char *p = (char*)malloc(size) + size;
__asm__("movl %0, %%esp
" :: "r"(p));
//freopen("poetry.in","r",stdin);
//freopen("poetry.out","w",stdout);
in(n),in(T),in(K);
for(Re i=2;i<=n;++i)in(fa[i]),add(fa[i],i);
T1.kill(1);
for(Re i=1;i<=T;++i)in(Q[i].x),in(Q[i].y),Q[i].id=i;
sort(Q+1,Q+T+1);
sakura();
for(Re i=1;i<=T;++i)printf("%d
",Ans[i]);
fclose(stdin);
fclose(stdout);
}