• [冲刺国赛2022] 模拟赛6


    区间第k小

    题目描述

    给定一个长度为 \(n\) 的序列,每个位置的值在 \([0,n)\) 这个范围中。\(q\) 次询问某一区间,将所有在区间中出现次数超过 \(w\) 的数字视为数字 \(n\),求区间第 \(k\) 小是多少。

    注意:\(w\) 是一开始给定的,\(k\) 是随着询问而变化的。

    \(n,q,w\leq 10^5\),强制在线。

    解法

    首先考虑离线怎么做(被部分分诈骗了,一直在想 \(w=1\) 怎么做),移动右端点,维护所有左端点的权值线段树。当新加入的值是 \(x\) 时,如果当前的出现次数 \(\leq w\) 可以直接在区间 \([1,i]\) 中添加 \(1\)\(x\);否则找到往前 \(w\) 个数的位置 \(a\) 和往前 \(w+1\) 个数的位置 \(b\),在区间 \((a,i]\) 添加 \(1\)\(x\),在区间 \((b,a]\) 中添加 \(-w\)\(x\)

    所以可以直接用树套树维护,注意外层的区间线段树要标记永久化,内层的权值线段树只需要单点修改。询问时取出一条链上的权值线段树,在 \(\tt log\) 棵权值线段树上二分即可。

    转强制在线的方法就是把这个树套树也可持久化,注意为了让各个版本互不影响,内外层线段树都需要可持久化。这并不需要高超的技巧,只需要在修改时暴力复制即可,时间复杂度 \(O(n\log^2 n)\)

    #include <cstdio>
    #include <iostream>
    #include <queue>
    using namespace std;
    const int M = 100005;
    const int N = 80*M;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,w,q,ty,cnt,p[M],zz[M],rt[N],ls[N],rs[N];
    queue<int> s[M];
    namespace tree
    {
        const int N = 440*M;
        int cnt,ls[N],rs[N],s[N];
        int copy(int &x)
        {
            int y=++cnt;ls[y]=ls[x];rs[y]=rs[x];
            s[y]=s[x];return y;
        }
        void ins(int &x,int l,int r,int y,int c)
        {
            x=copy(x);s[x]+=c;
            if(l==r) return ;
            int mid=(l+r)>>1;
            if(mid>=y) ins(ls[x],l,mid,y,c);
            else ins(rs[x],mid+1,r,y,c);
        }
    }
    int copy(int &x)
    {
        int y=++cnt;ls[y]=ls[x];rs[y]=rs[x];
        rt[y]=rt[x];return y;
    }
    void ins(int &x,int l,int r,int L,int R,int y,int c)
    {
        if(L>r || l>R) return ;
        x=copy(x);
        if(L<=l && r<=R)
        {
            tree::ins(rt[x],0,n,y,c);
            return ;
        }
        int mid=(l+r)>>1;
        ins(ls[x],l,mid,L,R,y,c);
        ins(rs[x],mid+1,r,L,R,y,c);
    }
    int ask(int x,int y,int k)
    {
        static int a[30]={};int m=0,sum=0;
        for(int i=x,l=1,r=n;;)
        {
            int mid=(l+r)>>1;
            a[++m]=rt[i];
            sum+=tree::s[a[m]];
            if(l==r) break;
            if(mid>=y) i=ls[i],r=mid;
            else i=rs[i],l=mid+1;
        }
        //printf("%d\n",sum);
        if(sum<k) return n;
        for(int l=0,r=n;l<=r;)
        {
            if(l==r) return l;
            sum=0;int mid=(l+r)>>1;
            for(int i=1;i<=m;i++)
                sum+=tree::s[tree::ls[a[i]]];
            if(sum>=k)
            {
                for(int i=1;i<=m;i++)
                    a[i]=tree::ls[a[i]];
                r=mid;
            }
            else
            {
                for(int i=1;i<=m;i++)
                    a[i]=tree::rs[a[i]];
                l=mid+1;k-=sum;
            }
        }
        return -1;
    }
    signed main()
    {
    	freopen("kth.in","r",stdin);
        freopen("kth.out","w",stdout);
        n=read();w=read();q=read();ty=read();
        for(int i=1;i<=n;i++)
        {
            int x=read();
            s[x].push(i);zz[i]=zz[i-1];
            if(s[x].size()<=w)
                ins(zz[i],1,n,1,i,x,1);
            else
            {
                int h=s[x].front();s[x].pop();
                ins(zz[i],1,n,h+1,i,x,1);
                ins(zz[i],1,n,p[x]+1,h,x,-w);
                p[x]=h;
            }
        }
        for(int i=1,ans=0;i<=q;i++)
        {
            int l=read(),r=read(),k=read();
            if(ty) l^=ans,r^=ans,k^=ans;
            ans=ask(zz[r],l,k);
            printf("%d\n",ans);
        }
    }
    

    题目描述

    给定一棵 \(n\) 个点的树,若按照 \(u\rightarrow v\) 的方向经过边 \((u,v)\),如果 \(u<v\),则会让两个点的点权都增加 \(1\);如果 \(u>v\),则会让两个点的点权都减少 \(1\)

    现在给定每个点最终的点权,尝试构造 \(m\) 个路径 \((x_1,y_1),(x_2,y_2)...(x_m,y_m)\),使得经过这些路径之后能对应给定的点权。在 \(m\) 最小的情况下,要求 \(x_1,y_1,x_2,y_2...x_m,y_m\) 的字典序最小。

    \(n\leq 10^6\),数据保证答案的 \(m\leq n\)

    解法

    首先考察链的情况,发现 \(m\) 最小的策略就是能延伸多长就延伸多长,但是不能与已有的点权方向相反。(比如如果已有的点权是正,那么构造过程中就不能让它减小)

    推广到树的情况就是,从叶子往上构造,用合并子树链的方法,可以方便地计算出 \(m\) 的最小值。为了保证最小字典序,我们按照字典序枚举路径,然后检测添加这条路径之后路径总数是否仍然最小,就获得了 \(O(n^3)\) 的做法。

    关键的 \(\tt observation\) 是:两种方案 A->B,C->DA->D,C->B 产生的效果是一样的。这说明起点和终点内部是可以任意交换的,那么我们从叶子往上求出每个点作为起点或者终点的次数,然后分别排序即可,时间复杂度 \(O(n\log n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 1000005;
    #define pb push_back
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,a[M],b[M];vector<int> g[M],x,y;
    void dfs(int u,int fa)
    {
        for(int v:g[u]) if(v^fa)
        {
            dfs(v,u);
            a[u]-=a[v];
            int t=(u<v)?a[v]:-a[v];
            b[u]+=t;b[v]-=t;
        }
    }
    signed main()
    {
    	freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
        n=read();
        for(int i=1;i<=n;i++) a[i]=read();
        for(int i=1;i<n;i++)
        {
            int u=read(),v=read();
            g[u].pb(v);g[v].pb(u);
        }
        dfs(1,0);
        for(int i=1;i<=n;i++)
        {
            int f=b[i]>0;b[i]=b[i]>0?b[i]:-b[i];
            while(b[i]--)
            {
                if(f) x.pb(i);
                else y.pb(i);
            } 
        }
        sort(x.begin(),x.end());
        sort(y.begin(),y.end());
        printf("%d\n",x.size());
        for(int i=0;i<x.size();i++)
            printf("%d %d\n",x[i],y[i]);
    }
    
  • 相关阅读:
    ORA01102 cannot mount database in EXCLUSIVE mode
    VC中cl.exe命令参数详解
    (转)LIB和DLL的区别与使用
    (转)DOS循环:bat/批处理for命令详解之一 (史上虽详尽的总结和说明~~)
    常用Win IDE库函数
    (转)Windows下多媒体计时器使用举例
    (转)C++进阶必读书籍
    (转)sql 行专列 列转行 普通行列转换
    VLFeat(1)——SIFT图像特征提取(VC++实现)
    (转)SIFT算法研究
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16375233.html
Copyright © 2020-2023  润新知