zoukankan      html  css  js  c++  java
  • 【题解】毒瘤 OI 刷题汇总 [SCOI2015]

    【题解】毒瘤 OI 刷题汇总 [SCOI2015]

    由于不清楚题目顺序,就按照 ( ext{BZOJ}) 上面的排列好了。


    【Day1 T1】小凸玩矩阵

    传送门:小凸玩矩阵 ( ext{[P4251]}) ( ext{[Bzoj4443]})

    【题目描述】

    给出一个 (n imes m) ((1leqslant nleqslant mleqslant 250)) 的矩阵 (a),现要从中选出 (n) 个数,且满足任意两个数都不能在同一行或同一列。求选出的 (n) 个数中第 (k) ((1leqslant kleqslant n)) 大的数最下可以为多少。

    【分析】

    二分+最大匹配判定。

    求第 (k) 大最小很明显可以二分,判定二分值 (mid) 的优劣性时可以上最大匹配。由于答案要尽量小,所以大于 (mid) 的数要尽量少选,小于等于 (mid) 的数要尽量多选,考虑使用后者作为判定依据(方便跑最大匹配)。

    在每次判定中,对于 (a_{i,j}leqslant mid) 的位置,(i)(j) 连边,跑出的最大匹配就是 小于等于 (mid) 的数最多可以选出的数量。

    设最终答案为 (ans),如果至少可以选出 (n-k+1) 个(最大匹配 (geqslant n-k+1)),说明 (midgeqslant ans),缩小上界,反之扩大下界。

    跑最大匹配我一般都用 (dinic),复杂度更优一点。但这道题用匈牙利貌似也能过。

    时间复杂度:(O(n^2log n))

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LD double
    #define LL long long
    #define Vector Point
    #define Re register int
    using namespace std;
    const int N=505,M=63005,inf=2e9;
    int n,m,l,r,K,st,ed,A[253][253];
    inline void in(Re &x){
        int f=0;x=0;char c=getchar();
        while(c<'0'||c>'9')f|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=f?-x:x;
    }
    struct Dinic{
        int o,h,t,maxflow,Q[N],dis[N],cur[N],head[N];
        inline void CL(){memset(head,0,sizeof(head)),maxflow=0,o=1;}
        struct QAQ{int to,next,flow;}a[M<<1];
        inline void add_(Re x,Re y,Re flow){a[++o].to=y,a[o].flow=flow,a[o].next=head[x],head[x]=o;}
        inline void add(Re x,Re y,Re flow){add_(x,y,flow),add_(y,x,0);}
        inline int bfs(Re st,Re ed){
            for(Re i=0;i<=ed;++i)dis[i]=0,cur[i]=head[i];
            h=1,t=0,Q[++t]=st,dis[st]=1;
            while(h<=t){
                Re x=Q[h++];
                for(Re i=head[x],to;i;i=a[i].next)
                    if(a[i].flow&&!dis[to=a[i].to]){
                        dis[to]=dis[x]+1,Q[++t]=to;
                        if(to==ed)return 1;
                    }
            }
            return 0;
        }
        inline int dfs(Re x,Re flow){
            if(x==ed||!flow)return flow;
            Re tmp=0,to,f;
            for(Re i=cur[x];i;i=a[i].next){
                cur[x]=i;
                if(dis[to=a[i].to]==dis[x]+1&&(f=dfs(to,min(flow-tmp,a[i].flow)))){
                    a[i].flow-=f,a[i^1].flow+=f,tmp+=f;
                    if(flow==tmp)break;
                }
            }
            return tmp;
        }
        inline void dinic(Re st,Re ed){while(bfs(st,ed))maxflow+=dfs(st,inf);}
    }T1;
    inline int judge(Re X){
        T1.CL(),st=n+m+1,ed=st+1;
        for(Re i=1;i<=n;++i)
            for(Re j=1;j<=m;++j)
                if(A[i][j]<=X)T1.add(i,n+j,1);
        for(Re i=1;i<=n;++i)T1.add(st,i,1);
        for(Re i=1;i<=m;++i)T1.add(n+i,ed,1);
        T1.dinic(st,ed);
        return T1.maxflow>=n-K+1;
    }
    int main(){
    //    freopen("123.txt","r",stdin);
        in(n),in(m),in(K),l=inf,r=0;
        for(Re i=1;i<=n;++i)
            for(Re j=1;j<=m;++j)
                in(A[i][j]),l=min(l,A[i][j]),r=max(r,A[i][j]);
        while(l<r){
            Re mid=l+r>>1;
            if(judge(mid))r=mid;
            else l=mid+1;
        }
        printf("%d
    ",r);
    }
    

    【Day1 T2】国旗计划

    传送门:国旗计划 ( ext{[P4155]}) ( ext{[Bzoj4444]})

    【题目描述】

    有一个大小为 (m) ((m<10^9)) 的环,环上的点编号分别为 (1,2,3...m),现给出 (n) ((nleqslant 2*10^5)) 个线段(不会有互相包含的情况),您需要选出最少的线段覆盖整个环。求在必选线段 (i) 时所需要的最小线段个数。

    【分析】

    暴力倍增。

    按照套路先将环复制一遍变成链。

    把所有线段按照左端点排个序,对于每一个线段 (i),右边与它相交的线段都可以作为接力的候选人,但由于线段不会互相包含,所以直接贪心选最右边的那一个。这个东西直接用一个指针扫过去就搞定了。

    考虑对于每次询问 (i),以线段 (i) 的左端点作为起点,然后不断地往后跳,直至区间右端点大于等于 (i+m) 时就结束。酱紫的暴力当然是不行的,但可以用倍增优化跳跃的过程。

    时间复杂度:(O(nlog n))

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define LL long long
    #define Re register int
    using namespace std;
    const int N=2e5+3,logN=19;
    int n,m,x,y,nex[N<<1],Ans[N],f[N<<1][20];
    struct QAQ{int l,r,id;inline bool operator<(const QAQ &O)const{return l<O.l;};}A[N<<1];
    inline void in(Re &x){
        int f=0;x=0;char c=getchar();
        while(c<'0'||c>'9')f|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=f?-x:x;
    }
    int main(){
    //    freopen("123.txt","r",stdin);
        in(n),in(m);
        for(Re i=1;i<=n;++i){
            in(x),in(y);if(x>y)y+=m;
            A[i]=(QAQ){x,y,i},A[i+n]=(QAQ){x+m,y+m,i};
    //注意所有区间都必须复制一遍(右端点大于m的也必须加,否则洛咕第2个点会WA)
        }
        n<<=1,sort(A+1,A+n+1);
        for(Re i=1,j=1;i<=n;++i){
            while(j<n&&A[j+1].l<=A[i].r)++j;
            nex[i]=j;if(i<j)f[i][0]=j;
        }
        for(Re j=1;j<=logN;++j)
            for(Re i=1;i+(1<<j)<=n;++i)
                f[i][j]=f[f[i][j-1]][j-1];
        for(Re i=1;i<=(n>>1);++i){
            Re tmp=1,now=i;
            for(Re j=logN;j>=0;--j)
                if(f[now][j]&&A[f[now][j]].r<A[i].l+m)
                    tmp+=(1<<j),now=f[now][j];
            Ans[A[i].id]=tmp+1;
        }
        for(Re i=1;i<=(n>>1);++i)printf("%d ",Ans[i]);
    }
    

    【Day1 T3】小凸想跑步

    传送门:小凸想跑步 ( ext{[P4250]}) ( ext{[Bzoj4445]})

    【题目描述】

    给出一个 (n) ((nleqslant 10^5)) 个点的凸包,在凸包内任选一个点 (A),将 (A) 与凸包上的点 (P_{iin[1,n]}) 连边,构成 (n) 个三角形。设 (P_{n+1}=P_1),若对于任意的 (iin[2,n]) 均满足 (S_{Delta AP_1P_2}leqslant S_{Delta AP_iP_{i+1}}),则称点 (A) 合法。求任选一个点的合法概率。

    【分析】

    暴力化简柿子 + 半平面交。

    考虑将上面的不等式展开(注意 (x imes y=-y imes x)):

    [S_{Delta AP_1P_2}leqslant S_{Delta AP_iP_{i\%n+1}}\ (P_1-A) imes(P_2-A)leqslant (P_i-A) imes(P_{i+1}-A)\ P_1 imes P_2 +P_1 imes (-A)+(-A) imes P2leqslant P_i imes P_{i+1} +P_i imes (-A)+(-A) imes P{i+1}\ P_1 imes P_2+A imes (P_1-P_2)leqslant P_i imes P_{i+1}+A imes (P_i-P_{i+1})\ A imes (P_1-P_2-P_i+P_{i+1})+P_1 imes P_2-P_i imes P_{i+1}leqslant 0 ]

    (A(x,y)),则上述式子可表示为 (ax+by+cleqslant 0) 的形式,其中 (a=(P_1-P_2-P_i+P_{i+1})_y,) (b=-(P_1-P_2-P_i+P_{i+1})_x,) (c=P_1 imes P_2-P_i imes P_{i+1}),用半平面交把这堆不等式方程组解出来就可以得到合法区域。

    具体来说,对于每个 (iin[2,n]):讨论一下 (a,b) 的正负性,分别选择合适的半平面向量,最后再把凸多边形的边界也加进去(要求选点只能在凸包里面),用解出的小凸包与原凸包面积求比值即为答案。

    时间复杂度:(O(nlog n))

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LD double
    #define LL long long
    #define Vector Point
    #define Re register int
    using namespace std;
    const int N=1e5+3,inf=2e9;
    const LD eps=1e-8;
    inline int dcmp(LD a){return a<-eps?-1:(a>eps?1:0);}
    inline void in(Re &x){
        int f=0;x=0;char c=getchar();
        while(c<'0'||c>'9')f|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=f?-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 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 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 int judge(Point a,Point L,Point R){//判断AL是否在AR右边
        return dcmp(Cro(L-a,R-a))>0;
    }
    inline Point cross(Point a,Point b,Point c,Point d){
        Vector x=b-a,y=d-c,z=a-c;
        return a+x*(Cro(y,z)/Cro(x,y));
    }
    inline LD PolyArea(Point *P,Re n){
        LD ans=0;
        for(Re i=1;i<=n;++i)ans+=Cro(P[i],P[i<n?i+1:1]);
        return ans/2.0;
    }
    struct Line{
        Point a,b;LD k;Line(Point A=Point(0,0),Point B=Point(0,0)){a=A,b=B,k=atan2(b.y-a.y,b.x-a.x);}
        inline bool operator<(const Line &O)const{return dcmp(k-O.k)?dcmp(k-O.k)<0:judge(O.a,O.b,b);}
    }Q[N<<1];
    inline int judge(Point a,Line L){return dcmp(Cro(a-L.a,L.b-L.a))>0;}//判断点a是否在L右边
    inline Point cross(Line L1,Line L2){return cross(L1.a,L1.b,L2.a,L2.b);}
    inline int halfcut(Line *L,Re n,Point *P){
        sort(L+1,L+n+1);Re m=0;
        for(Re i=1;i<=n;++i)if(i==1||dcmp(L[i].k-L[i-1].k))L[++m]=L[i];
        Re h=1,t=0;
        for(Re i=1;i<=m;++i){
            while(h<t&&judge(cross(Q[t],Q[t-1]),L[i]))--t;
            while(h<t&&judge(cross(Q[h],Q[h+1]),L[i]))++h;
            Q[++t]=L[i];
        }
        while(h<t&&judge(cross(Q[t],Q[t-1]),Q[h]))--t;
        while(h<t&&judge(cross(Q[h],Q[h+1]),Q[t]))++h;
        m=0;
        for(Re i=h;i<=t;++i)P[++m]=cross(Q[i],Q[i<t?i+1:h]);
        return m;
    }
    int n,m;Point P[N],PP[N<<1];Line L[N<<1];
    int main(){
    //    freopen("123.txt","r",stdin);
        in(n);
        for(Re i=1;i<=n;++i)P[i].in();P[n+1]=P[1];
        for(Re i=2;i<=n;++i){
            Point tmp=P[1]-P[2]-P[i]+P[i+1];
            LD a=tmp.y,b=-tmp.x,c=Cro(P[1],P[2])-Cro(P[i],P[i+1]);//ax+by+c<=0
            if(!dcmp(a)&&!dcmp(b)){
                if(dcmp(c)<=0)continue;
                else{puts("0");return 0;}
            }
            if(!dcmp(a)){//by+c<=0
                if(b>0)L[++m]=Line(Point(1,-c/b),Point(0,-c/b));//y<=-c/b
                else L[++m]=Line(Point(0,-c/b),Point(1,-c/b));//y>=-c/b
            }
            else if(!dcmp(b)){//ax+c<=0
                if(a>0)L[++m]=Line(Point(-c/a,0),Point(-c/a,1));//x<=-c/a
                else L[++m]=Line(Point(-c/a,1),Point(-c/a,0));//x>=-c/a
            }
            else if(b>0)L[++m]=Line(Point(1,-a/b-c/b),Point(0,-c/b));//y<=-a/b*x-c/b
            else L[++m]=Line(Point(0,-c/b),Point(1,-a/b-c/b));//y>=-a/b*x-c/b
        }
        for(Re i=1;i<=n;++i)L[++m]=Line(P[i],P[i+1]);
        Re cnt=halfcut(L,m,PP);
        printf("%.4lf
    ",PolyArea(PP,cnt)/PolyArea(P,n));
    }
    

    【Day2 T1】小凸玩密室

    传送门:小凸玩密室 ( ext{[P4253]}) ( ext{[Bzoj4446]})

    【题目描述】

    给出一颗 (n) ((nleqslant 2*10^5)) 个点的完全二叉树,其中点 (i) 的权值为 (A[i]) 。现要给所有点染色,染第一个点不需要花费,在这之后每染一个新点 (y),设上次染色的点为 (x),则染 (y) 的花费为 (dis(x,y)*A_y) 。要求在任意时刻已被染色的点必须连通,且每次给任意一个点 (x) 染色后,必须要把 (x) 的子树内所有点都染完后才能染 (x) 子树外的点。

    【分析】

    毒瘤树形 (dp)

    (pl)(x) 的左儿子,(pr)(x) 的右儿子,({son_i}) 为点 (i) 子树内的点组成的集合。

    (f[x][y]) 表示从 (x) 出发,染完 (x) 的子树后到达 (y) 点的最小花费,则有 ((to) 为点 (x) 子树中的叶节点或者缺了一个儿子的点 ())

    [f[x][y]=minegin{cases} dis(x,pr)*A[pr]+f[pr][to]+dis(to,pl)*A[pl]+f[pl][y], yin {son_{pl}}\ dis(x,pl)*A[pl]+f[pl][to]+dis(to,pr)*A[pr]+f[pr][y], yin {son_{pr}} end{cases} ]

    考虑把第一个式子里的 (dis(to,pl)) 展开,变成 (dis(to,x)+dis(x,pl)),得到 (f[x][y]=dis(x,pr)*A[pr]+dis(x,pl)*A[pl]) (+min{f[pr][to]+dis(to,x)*A[pl]+f[pl][y]}),发现 (f[pr][to]+dis(to,x)*A[pl]) 可以提前扫描一遍预处理得出,于是复杂度便降到了 (O(sum_{i=1}^{n} ext{点} i ext{子树内缺儿子的点的个数})=O(nlog n)) 。第二个式子同理。

    由于无法确定开始染色的第一个点,所以还要再设一个方程,用 (g[x][y]) 表示以 (x) 子树内的任意一点作为起点,最后到达 (y) 所需的最小花费,则有:

    [g[x][y]=minegin{cases} f[x][y]\ g[pr][to]+dis(to,x)*A[x]+dis(x,pl)*A[pl]+f[pl][y], yin {son_{pl}}\ g[pl][to]+dis(to,x)*A[x]+dis(x,pr)*A[pr]+f[pr][y], yin {son_{pr}} end{cases} ]

    发现下面那两个柿子也可以用类似上面 (f) 的做法进行优化。

    由于完全二叉树良好的性质,复杂度均为 (O(nlog n))

    【Code】

    一开始用了 (map) 存状态(好写一点),(loj) 轻松水过,洛谷 (MLE+TLE),只好换成 (vector),结果又调了好长时间。

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    #include<map>
    #define LL long long
    #define Re register LL
    using namespace std;
    const LL N=2e5+3,inf=1e18;
    LL n,A[N],B[N][2],deep[N];vector<LL>f[N],g[N],son[N];
    inline void in(Re &x){
        int f=0;x=0;char c=getchar();
        while(c<'0'||c>'9')f|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=f?-x:x;
    }
    #define pl (x<<1)
    #define pr (x<<1|1)
    inline int dis(Re x,Re y){return x>y?deep[x]-deep[y]:deep[y]-deep[x];}
    inline void dfs(Re x){
        deep[x]=deep[x>>1]+B[x>>1][x&1];
        if(!B[x][0]){//叶子节点
            Re p=x;while(p)son[p].push_back(x),f[p].push_back(0),g[p].push_back(0),p>>=1;
        }
        else if(!B[x][1]){//只有左儿子
            dfs(pl);
            Re p=x;while(p)son[p].push_back(x),f[p].push_back(0),g[p].push_back(0),p>>=1;
            f[x][0]=dis(x,pl)*A[pl],g[x][0]=dis(x,pl)*A[pl];//son[x][0]: pl
            f[x][1]=inf,g[x][1]=dis(x,pl)*A[x];//son[x][1]: x
        }
        else{//有两个儿子
            dfs(pl);Re cut=son[x].size();dfs(pr);Re f0=inf,f1=inf,g0=inf,g1=inf;
            for(Re i=0,to,m=son[x].size();i<m;++i){
                to=son[x][i];
                if(i<cut){//to在左子树
                    f0=min(f0,f[pl][i]+dis(to,x)*A[pr]);
                    g0=min(g0,g[pl][i]+dis(to,x)*A[x]);
                }
                else{//to在右子树
                    f1=min(f1,f[pr][i-cut]+dis(to,x)*A[pl]);
                    g1=min(g1,g[pr][i-cut]+dis(to,x)*A[x]);
                }
            }
            Re tmp=dis(x,pl)*A[pl]+dis(x,pr)*A[pr];
            for(Re i=0,to,m=son[x].size();i<m;++i){
                to=son[x][i];
                if(i<cut){//to在左子树
                    f[x][i]=tmp+f[pl][i]+f1;
                    g[x][i]=min(f[x][i],dis(x,pl)*A[pl]+f[pl][i]+g1);
                }
                else{//to在右子树
                    f[x][i]=tmp+f[pr][i-cut]+f0;
                    g[x][i]=min(f[x][i],dis(x,pr)*A[pr]+f[pr][i-cut]+g0);
                }
            }
        }
    }
    int main(){
    //    freopen("123.txt","r",stdin);
        in(n);
        for(Re i=1;i<=n;++i)in(A[i]);
        for(Re i=1;i<n;++i)in(B[i+1>>1][(i&1)^1]);
        dfs(1);Re ans=inf;
        for(Re i=0,m=son[1].size();i<m;++i)ans=min(ans,g[1][i]);
        printf("%lld
    ",ans);
    }
    

    【Day2 T2】小凸解密码

    传送门:小凸解密码 ( ext{[P5226]}) ( ext{[Bzoj4447]})

    【题目描述】

    给出一个大小为 (n) ((nleqslant 2*10^5)) 的环,环上每个点有两种权值 ((x,ch)),其中 (xin[0,9])(ch) 为 '(+)' 或 '(*)'。

    有若干次操作,每次操作有两种情况:

    • (1 x y ch:) 将第 (x+1) 个点修改为 ((y,ch))

    • (1 x:)(x+1) 开始,按照顺时针将两种权值分别记录到 (A) 数组和 (C) 中,则 (B_i=egin{cases}A_1 (i=1)\(A_i+A_{i-1})\%10 (i>1,C_i=‘+’)\ (A_i*A_{i-1})\%10 (i>1,C_i=‘*’)end{cases}),然后将 (B) 数组连城成一个环,在这个环上会有一些 (0) 连在一起,将这些连在一起的 (0) 称为零区间,求距离 (B_1)​ 最远的零区间,并输出这个距离(零区间和 (B_1) 的距离定义为:零区间内所有 (0)(B_1)​ 距离中的最小值)。

    【分析】

    暴力二分+线段树瞎搞。

    首先要把环复制一下变为链(一年考两个环,不愧为毒瘤 (OI)),考虑用线段树维护 (B) 数组,每次 (1) 操作最多只会影响到四个位置 ((B_x,B_{x+1},B_{x+n},B_{x+n+1})),暴力修改即可。

    考虑解决询问,求最小的最大明显可以二分,设当前的二分值为 (mid),则需要检查 ([x+mid,x+n-1-mid+1]) 里是否有一段全为 (0) 的子区间(需要满足该子区间左右两边均不为 (0)),但这个东西很不好求,考虑把询问区间分别向左右两边扩大一格得到 ([ql,qr]),然后查询 ([ql,qr]) 内是否存在 左端点大于 (ql) 且 右端点小于 (qr) 的全 (0) 子区间即可。

    时间复杂度:(O(nlog^2 n))

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define LL long long
    #define Re register int
    using namespace std;
    const int N=1e5+3,inf=2e9;
    int n,x,y,T,op,A[N<<1];char ch,type[N<<1];
    inline void in(Re &x){
        int f=0;x=0;char c=getchar();
        while(c<'0'||c>'9')f|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=f?-x:x;
    }
    inline int calc(Re i){return i?(type[i]=='+'?(A[i]+A[i-1])%10:A[i]*A[i-1]%10):A[i];}
    struct Segment_Tree{
        #define pl (p<<1)
        #define pr (p<<1|1)
        #define mid ((L+R)>>1)
        struct QAQ{
            int S;bool l,r,ans;
            inline QAQ operator+(const QAQ &R){
                QAQ p;p.S=S+R.S,p.l=l,p.r=R.r;
                p.ans=ans||R.ans||(S&&R.S&&(!r||!R.l));
                return p;
            }
        }tr[N<<3];
        inline void updata(Re p,Re x,Re v=-1){
            tr[p].ans=0,tr[p].l=tr[p].r=tr[p].S=((v==-1?calc(x):v)>0);
        }
        inline void build(Re p,Re L,Re R){
            if(L==R){updata(p,L);return;}
            build(pl,L,mid),build(pr,mid+1,R);
            tr[p]=tr[pl]+tr[pr];
        }
        inline void refresh(Re p,Re L,Re R,Re x){
            if(L==R){updata(p,L);if(x<=n)refresh(1,1,n<<1,x+n);return;}
            if(x<=mid)refresh(pl,L,mid,x);
            else refresh(pr,mid+1,R,x);
            tr[p]=tr[pl]+tr[pr];
        }
        inline void change(Re p,Re L,Re R,Re x,Re v){
            if(L==R){updata(p,x,v);return;}
            if(x<=mid)change(pl,L,mid,x,v);
            else change(pr,mid+1,R,x,v);
            tr[p]=tr[pl]+tr[pr];
        }
        inline QAQ ask(Re p,Re L,Re R,Re l,Re r){
            if(l<=L&&R<=r)return tr[p];
            if(r<=mid)return ask(pl,L,mid,l,r);
            else if(l>mid)return ask(pr,mid+1,R,l,r);
            else return ask(pl,L,mid,l,r)+ask(pr,mid+1,R,l,r);
        }
    }T1;
    int main(){
    //    freopen("456.txt","r",stdin);
        in(n),in(T);
        for(Re i=1;i<=n;++i)in(A[i]),scanf("%c",&type[i]),A[i+n]=A[i],type[i+n]=type[i];
        T1.build(1,1,n<<1);
        while(T--){
            in(op),in(x),++x;
            if(op<2){
                in(y),scanf("%c",&ch),A[x]=A[x+n]=y,type[x]=type[x+n]=ch,
                T1.refresh(1,1,n<<1,x),T1.refresh(1,1,n<<1,x+1);
            }
            else{
                Re L=x,R=x+n-1,ans=-1;
                if(!A[L])ans=0;
                T1.change(1,1,n<<1,L,A[L]);
                Re l=1,r=(n+1)/2;
                while(l<=r){
                    Re Mid=l+r>>1;
                    if(T1.ask(1,1,n<<1,L+Mid-1,R-Mid+1+1).ans)ans=Mid,l=Mid+1;
                    else r=Mid-1;
                }
                T1.change(1,1,n<<1,L,calc(L));
                printf("%d
    ",ans);
            }
        }
    }
    

    【Day2 T3】情报传递

    传送门:情报传递 ( ext{[P4216]}) ( ext{[Bzoj4448]})

    【题目描述】

    给出一颗 (n) ((nleqslant 2*10^5)) 个点的有根树,有若干个操作,操作种类如下:

    • (1 x y c:) 设当前为第 (i) 的操作,分别查询从 (x)(y) 的链上点的个数、满足 (i-st_j>c)(j) 的个数

    • (2 x:) 设当前为第 (i) 的操作,设置 (st_x=i)

    【分析】

    小学生级别的主席树大水题。

    询问第一个子问题直接求 (lca) 用各点深度相减即可,第二个子问题可转化为查询满足 (st_jleqslant i-c-1)(j) 的个数,直接在主席树上差分搞即可。

    可以发现没必要写带修的树套树版本,把所有操作 (2) 全部拿出来建好树,然后再去挨个处理询问,这样做是不影响正确性的(数据里没有出现同一个点多次进行操作 (2) 的情况)。

    时间复杂度:(O(nlog n))

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define LL long long
    #define Re register int
    using namespace std;
    const int N=2e5+3,logN=18,inf=2e9;
    int n,x,y,z,o,m,T,op,rt,A[N],st[N],fa[N],head[N],deep[N];
    struct QAQ{int to,next;}a[N];
    struct Query{int x,y,c,t;}Q[N];
    inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
    inline void in(Re &x){
        int f=0;x=0;char c=getchar();
        while(c<'0'||c>'9')f|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=f?-x:x;
    }
    struct LCA{
        int ant[N][20];
        inline void dfs(Re x){
            deep[x]=deep[ant[x][0]=fa[x]]+1;
            for(Re i=1;(1<<i)<=deep[x];++i)ant[x][i]=ant[ant[x][i-1]][i-1];
            for(Re i=head[x];i;i=a[i].next)dfs(a[i].to);
        }
        inline int lca(Re x,Re y){
            if(deep[x]<deep[y])swap(x,y);
            for(Re i=logN;i>=0;--i)if(deep[ant[x][i]]>=deep[y])x=ant[x][i];
            if(x==y)return x;
            for(Re i=logN;i>=0;--i)if(ant[x][i]!=ant[y][i])x=ant[x][i],y=ant[y][i];
            return ant[x][0];
        }
    }T1;
    int pt[N];
    struct Segment_Tree{
        #define pl (tr[p].lp)
        #define pr (tr[p].rp)
        #define mid ((L+R)>>1)
        int O;
        struct QAQ{int S,lp,rp;}tr[N*logN<<2];
        inline void creat(Re &p,Re q,Re L,Re R,Re x){
            tr[p=++O]=tr[q],++tr[p].S;
            if(L==R)return;
            if(x<=mid)creat(pl,tr[q].lp,L,mid,x);
            else creat(pr,tr[q].rp,mid+1,R,x);
        }
        inline int ask(Re p,Re q,Re f1,Re f2,Re L,Re R,Re l,Re r){
            if(!p&&!q&&!f1&&!f2)return 0;
            if(l<=L&&R<=r)return tr[p].S+tr[q].S-tr[f1].S-tr[f2].S;
            Re ans=0;
            if(l<=mid)ans+=ask(pl,tr[q].lp,tr[f1].lp,tr[f2].lp,L,mid,l,r);
            if(r>mid)ans+=ask(pr,tr[q].rp,tr[f1].rp,tr[f2].rp,mid+1,R,l,r);
            return ans;
        }
    }TR;
    inline void dfs(Re x){
        TR.creat(pt[x],pt[fa[x]],1,T,st[x]);
        for(Re i=head[x];i;i=a[i].next)dfs(a[i].to);
    }
    int main(){
    //    freopen("123.txt","r",stdin);
        in(n);
        for(Re i=1;i<=n;++i){
            in(fa[i]);
            if(fa[i])add(fa[i],i);
            else rt=i;
        }
        in(T),T1.dfs(rt);
        for(Re i=1;i<=n;++i)st[i]=T;//st要初始化为一个极大值(设到T就够了)
        for(Re i=1;i<=T;++i){
            in(op);
            if(op>1)in(x),st[x]=i;
            else in(Q[++m].x),in(Q[m].y),in(Q[m].c),Q[m].t=i;
        }
        dfs(rt);
        for(Re i=1;i<=m;++i){
            Re lca=T1.lca(Q[i].x,Q[i].y);
            printf("%d %d
    ",deep[Q[i].x]+deep[Q[i].y]-deep[lca]-deep[fa[lca]],TR.ask(pt[Q[i].x],pt[Q[i].y],pt[lca],pt[fa[lca]],1,T,1,Q[i].t-Q[i].c-1));
        }
    }
    

  • 相关阅读:
    RESTful API 介绍,设计
    golang web框架设计7:整合框架
    golang web框架设计6:上下文设计
    golang web框架设计5:配置设计
    golang web框架设计4:日志设计
    golang web框架设计3:controller设计
    golang web框架设计2:自定义路由
    golang web框架设计1:框架规划
    深入理解golang: channels
    服务端高并发分布式十四次架构演进之路
  • 原文地址:https://www.cnblogs.com/Xing-Ling/p/12747697.html
Copyright © 2011-2022 走看看