【题解】GXOI/GZOI2019 机房游记

【题解】GXOI/GZOI2019 机房游记

教练不知道从哪里嫖来的数据(或许是洛谷或者\(Loj\)?),只说是让我们做套模拟题,后来发现是今年 \(\text{GX,GZ}\) 两省联考的考试题。

由于时间关系,只考 \(4\) 个小时。本来就不会做,还不给狗时间骗分,这是要爆蛋的节奏啊。

时间:2019-1-9 (机房 day1)

考前几分钟打了一局PvZ。

\(T1\) 貌似写的是正解,\(T2\) 直接放弃了,如果再多一个小时或许能骗一点分?\(T3\) 有一个部分用了我最爱的 \(\text{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】

上来先开 \(\text{T1}\) 码暴力。一开始把 \(or\) 看成了 \(xor\),想着直接上矩阵前缀和,而且居然还过了第一个样例,对着样例 \(2\) 手玩了好久才发现是应该是 \(or\)

我太菜了

回想起以前做过的一道题:\(\text{bob [COCI2015]}\),如果所有数字都是 \(0\) 或者 \(1\) 的话,就可以直接上单调栈扫过去,很明显这道题可以尝试一些转换。

由于 \(and\)\(or\) 运算对于二进制数字的每一位都是独立的,可以把所有数都拆成 \(31\)\(0\)\(1\),构造出 \(31\) 个新的矩阵。

对于每一个矩阵的求解:注意到 \(and\) 实际上就是统计有多少个全为 \(1\) 的子矩阵,而 \(or\) 则是总的子矩阵个数 \(\left(\frac{n(n+1)}{2}\right)^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\n",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)\)”,这不就是个 \(\text{CDQ}\) 的板子操作吗?由于交点数不大于 \(5e5\),可以直接在递归中暴力枚举求交,时间复杂度为:\(O(nlogn+\) \(\text{交点数}\) \()\)

\((2).\) 计算被观测到的交点的数量。

先把所有的点都顺时针旋转 \(45^\circ\),然后做二维数点,可以用离线排序+扫描线+树状数组解决。

(注意 是否被观测 与 选择哪种特技表演 其实是相互独立的,两者可以分开处理)

\((3).\) 分别计算两种特技表演的次数。

每交换一次就相当于是把某个 \(A[i],A[j]\) 的值进行了交换,而最后的 \(A\) 序列一定是排好序的,是不是和求交点很像?模拟一下会发现:在所有的交点处都进行一次交换,最后的达到高度必定满足排名要求。因此当 \(a>b\) 时最大得分一定为 \(a\) \(*\) \(\text{交点个数}\)

下面考虑 \(a<b\) 的情况:用尽量少的交换次数满足到达高度的排名。将所有的 \(i\)\(A[i]\) 连边,最后一定会形成若干个环,每个大小为 \(x\) 的环都可以用 \(x-1\) 次交换(环中的点相互一共有 \(x-1\) 个交点)将其归位,而对于不同环中的点的交点则不用管,答案为:\(n-\) \(\text{环的个数}\) 。可以发现这样算出来即为最少需要的交换次数。

时间复杂度: \(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\n",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\n",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]+2\sum_{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",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\n",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\n" :: "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\n",Ans[i]);
    fclose(stdin);
    fclose(stdout);
}
posted @ 2020-01-12 16:16  辰星凌  阅读(200)  评论(0编辑  收藏  举报