牛客网NOIP赛前集训营-提高组(第一场)

题目链接~

A 中位数(二分)

Problem
求所有大于等于 len l e n 的区间的中位数最大可以是多少

Solution
我们二分一个答案,对于序列中大于的给 1 1 ,小于的给 1
若存在某一段的值大于等于 0 0 ,则说明这个答案成立
这个问题可以用前缀和来维护

Code

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
#define N 100010
int n,m,a[N],b[N],sum[N];
inline bool check(int x){
    for(int i=1;i<=n;i++)
        if(a[i]<x) sum[i]=sum[i-1]-1;
        else if(a[i]>x) sum[i]=sum[i-1]+1;
        else sum[i]=sum[i-1];
    int mn=100010;
    for(int i=m;i<=n;i++){
        mn=min(mn,sum[i-m]);
        if(sum[i]-mn>=0) return 1;
    } 
    return 0;
}
int main(){
    scanf("%d%d",&n,&m);
    int l=1000000001,r,ans;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),l=min(l,a[i]),r=max(r,a[i]);
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid)) l=mid+1,ans=mid;
        else r=mid-1;
    }
    printf("%d",ans);
    return 0;
}

B 数数字(记忆化搜索/dp)

Problem
l r r 的数字中,乘积在 l1 r1 r 1 之间的数有多少个

Solution
记忆化搜索…细节超多

Code

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
#define ll long long
int s[20];
ll l,r,l1,r1;
map<ll,ll>dp[20][2];
ll dfs(int pos,int lim,int pre,ll sum){    
    //前pos位已经处理完
    //lim=0 无数大小限制;lim=1 有大小限制 
    //pre=1 有前导零;pre=0 无前导零
    //sum表示目前的成绩 
    if(!pos) return l1<=sum && sum<=r1;    //搜索完成,看是否满足条件
    if(!lim && dp[pos][pre].count(sum)) return dp[pos][pre][sum]; 
    ll ans=0;
    for(int i=0;i<=(lim?s[pos]:9);i++){
        ans+=dfs(pos-1,lim && i==s[pos],pre&&(i==0),(pre&&(i==0)&&(pos!=1))?1:sum*i);
    }
    if(!lim) dp[pos][pre][sum]=ans;
    return ans;
}
ll solve(ll x){
    if(x==-1) return 0;
    int top=0;
    if(!x) top=1,s[1]=0; 
    while(x) s[++top]=x%10,x/=10;
    return dfs(top,1,1,1);
}
int main(){
    scanf("%lld%lld%lld%lld",&l,&r,&l1,&r1);
    printf("%lld\n",solve(r)-solve(l-1));
    return 0;
}

C 保护(lca+主席树)

Problem
有一棵 n n 个节点的树(1 为根), m m 个守卫,每个可以守护一段路径。
问对于一次询问 viki , 想找到一个离根节点最近的 ui u i 使得 vi v i ui u i 这条路径被大于等于 k k 个守卫守护。输出 vi ui u i 距离

Solution
由于查询只会向上不会向下,即 ui u i 一定是 vi v i 到根上的一点。
因此我们可以把守卫守护的路径变成 (xi,lca(xi,yi))(yi,lca(xi,yi)) ( x i , l c a ( x i , y i ) ) 、 ( y i , l c a ( x i , y i ) )
对于每次询问, (vi,ui) ( v i , u i ) ,如果有边 (xi,yi) ( x i , y i ) (直上直下)能完全覆盖它,那么 xi x i 一定为 vi v i 子树, yi y i 一定为 xi x i 到根上的一点
因此我们可以求出 dfs d f s 序,所有结果就是 vi v i 的子数中到符合条件最小的。我们可以二分这个点,判断是否有至少 k k 条路径。
这个可以用主席树来记录。
此时复杂度为 nlog2n

但其实结果我们只想求深度差。因此主席树中我们可以记录深度到为 x x 的有多少条边
那么结果就是 vi 的子数中第 k k <script type="math/tex" id="MathJax-Element-68">k</script> 小的

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; 
#define N 200010
int n,m,q,tot=0,num=0,Len=0,owo=0,in[N],out[N],h[N],lg[N],fa[N][20],dep[N],root[N],id[N];
struct node{int to,next;}edge[N<<1];
struct node1{int l,r,sum;}tree[N*200];
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while('0'<=ch && ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
inline void insert(int x,int y){
    edge[++num].to=y;edge[num].next=h[x];h[x]=num;
}
void dfs(int x){
    in[x]=++tot;id[tot]=x;
    for(int i=1;i<=lg[n];i++){
        if(!fa[x][i-1]) break;
        fa[x][i]=fa[fa[x][i-1]][i-1];
    }
    for(int i=h[x];i;i=edge[i].next){
        int y=edge[i].to;if(y==fa[x][0]) continue;
        dep[y]=dep[x]+1;fa[y][0]=x;
        Len=max(Len,dep[y]);dfs(y);
    }out[x]=tot;
}
inline int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    int len=dep[x]-dep[y];
    for(int i=0;i<=lg[len];i++){
        if((len>>i)&1) x=fa[x][i];
    }
    if(x==y) return x;
    for(int i=lg[n];i>=0;i--){
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    }
    return fa[x][0];
}
void add(int &p,int l,int r,int x){
    tree[++owo]=tree[p];p=owo;tree[owo].sum++;
    if(l==r) return;
    int mid=l+r>>1;
    if(x<=mid) add(tree[p].l,l,mid,x);
    else add(tree[p].r,mid+1,r,x);
}
int query(int p,int v,int l,int r,int k){
    if(tree[v].sum-tree[p].sum<k) return 0;
    if(l==r) return l;
    int mid=l+r>>1,t=tree[tree[v].l].sum-tree[tree[p].l].sum;
    if(t>=k) return query(tree[p].l,tree[v].l,l,mid,k);
    else return query(tree[p].r,tree[v].r,mid+1,r,k-t);
}
inline int solve(int x,int k){
    int y=query(root[in[x]-1],root[out[x]],1,Len,k);
    if(!y) return 0;
    return max(0,dep[x]-y);
}
int main(){
    n=read();m=read();
    lg[0]=-1;for(int i=1;i<N;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        insert(x,y);insert(y,x);
    }dep[1]=1;dfs(1);
    num=0;memset(h,0,sizeof(h));
    for(int i=1;i<=m;i++){
        int x=read(),y=read(),z=lca(x,y);
        insert(x,dep[z]),insert(y,dep[z]);
    }
    for(int i=1;i<=n;i++){
        root[i]=root[i-1];
        for(int j=h[id[i]];j;j=edge[j].next){
            add(root[i],1,Len,edge[j].to);
        }
    }
    q=read();
    for(int i=1;i<=q;i++){
        int x=read(),y=read();
        solve(x,y);
        printf("%d\n",solve(x,y));
    } 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值