题意
自己看吧
前言
pkusc这题的阴影太大,现在才做回。。
现在想起来,自己的方法过于复杂,并且忘记了使用标记永久化使得代码量巨增。。调不出来也是有原因的
题解
相比起我原来的方法,现在有一个很巧妙的
显然的结论,一个点只会往右边跳一次
然后我们发现这个跳一次非常地难搞。。
考场上我写得是两颗线段树搞来搞去。。搞得我头都大了
我们考虑,其实往右跳除了第一次都是不用代价的,因为你可以直接跳到这个点的右边啊。
因此,我们我们对于一个点,记录一个mnmn,表示他(可以先右跳一步)往左跳一步能到的最左
然后如果往右跳不算次数的话,直接从mnmn这里转移过来就可以了
至于查询的时候,也挺巧妙的
我们查询的是root[l[x]]root[l[x]]
我们假设先跳到这里,如果他往右跳了,那么就相当于第一步跳到那里就可以了
当然,答案要加上r?l+1r?l+1
CODE:
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=300005;
int n,q;
int a[N];
int mn[N];
LL c[N*40],lzy[N*40];
int s1[N*40],s2[N*40];
int num=0;
void change (int &now,int l,int r,int L,int R)
{num++;c[num]=c[now];lzy[num]=lzy[now];s1[num]=s1[now];s2[num]=s2[now];now=num;if (l==L&&r==R){lzy[now]++;return ;}c[now]=c[now]+(R-L+1);int mid=(l+r)>>1;if (R<=mid) change(s1[now],l,mid,L,R);else if (L>mid) change(s2[now],mid+1,r,L,R);else change(s1[now],l,mid,L,mid),change(s2[now],mid+1,r,mid+1,R);
}
LL ans;
void ask (int now,int l,int r,int L,int R)
{//printf("YES:%lld %lld %lld %lld %lld %lld\n",l,r,L,R,lzy[now],c[now]);ans=ans+lzy[now]*(R-L+1);if (l==L&&r==R) {ans=ans+c[now];return ;}int mid=(l+r)>>1;if (R<=mid) ask(s1[now],l,mid,L,R);else if (L>mid) ask(s2[now],mid+1,r,L,R);else ask(s1[now],l,mid,L,mid),ask(s2[now],mid+1,r,mid+1,R);
}
int rt[N];
LL gcd (LL x,LL y)
{return x==0?y:gcd(y%x,x);
}
int read()
{char ch=getchar();int x=0;while (ch<'0'||ch>'9') ch=getchar();while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x;
}
int main()
{
n=read();for (int u=2;u<=n;u++) a[u]=read();mn[n]=a[n];for (int u=n-1;u>=1;u--) mn[u]=min(a[u],mn[u+1]);for (int u=2;u<=n;u++){rt[u]=rt[mn[u]];change(rt[u],1,n,1,u-1);}q=read();for (int u=1;u<=q;u++){int l,r,x;l=read();r=read();x=read();ans=(r-l+1);if (l<a[x]) ask(rt[a[x]],1,n,l,min(r,a[x]-1));LL b=(r-l+1);LL d=gcd(b,ans);printf("%lld/%lld\n",ans/d,b/d);}return 0;
}