[冲刺国赛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]);
}
posted @ 2022-06-14 16:35  C202044zxy  阅读(224)  评论(0编辑  收藏  举报