zoukankan      html  css  js  c++  java
  • 10月28日考试 题解(贪心+二分+树形DP+期望+线段树)

    T1 饥饿的小鸟

    题目大意:有一条宽为$n$的河,左岸有一些鸟,距离左岸$0$到$n-1$的河上有一些石头,最多只能让$a_i$只鸟落下。鸟每次最多只能飞$l$个距离,问最多有几只鸟飞到河对岸。

    一开始写了个暴力递推,没想到A了??

    我的思路就是每次让鸟尽可能往远飞,然后直接递推转移即可。正确性显然。复杂度$O(nl)$,然而因为$a_ileq 10^4$所以遇到猛一点的评测姬也就卡过去了233,还跑得飞快~

    代码:

    #include<cstdio>
    #include<iostream>
    using namespace std;
    const int N=200005;
    const int inf=0x3f3f3f3f;
    int f[N],n,l,a[N],sum[N];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    signed main()
    {
        n=read();l=read();
        for (int i=1;i<n;i++) a[i]=read();
        a[n]=inf;
        for (int i=1;i<=l;i++) f[i]=a[i];
        for (int i=1;i<n;i++)
        {
            for (int j=i+l;j>=i+1;j--)
                if (a[j]-f[j]>=f[i]){
                    f[j]+=f[i];f[i]=0;
                    break;
                }else f[i]-=a[j]-f[j],f[j]=a[j];
        }
        printf("%d",f[n]);
        return 0;
        
    }

    T2 进化序列

    题目大意:给定长度为$n$的序列$a_i$,问有多少区间内所有元素的或值小于$m$。

    注意到每次进行或运算值是单调不降的,所以我们可以枚举右端点,然后二分出最远的左端点,同时用线段树查区间或和。时间复杂度$O(nlog^2 n)$。

    代码:

    #include<cstdio>
    #include<iostream>
    #define int long long
    using namespace std;
    const int N=100005;
    int a[N],n,m,ans;
    int sum[N<<2];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void build(int p,int l,int r)
    {
        if (l==r){
            sum[p]=a[l];
            return;
        }
        int mid=(l+r)>>1;
        build(p*2,l,mid);build(p*2+1,mid+1,r);
        sum[p]=sum[p*2]|sum[p*2+1];
    }
    inline int query(int p,int l,int r,int ql,int qr)
    {
        if (ql<=l&&r<=qr) return sum[p];
        int mid=(l+r)>>1,res=0;
        if (ql<=mid) res|=query(p*2,l,mid,ql,qr);
        if (qr>mid) res|=query(p*2+1,mid+1,r,ql,qr);
        return res;
    }
    signed main()
    {
        n=read();m=read();
        for (int i=1;i<=n;i++) a[i]=read();
        build(1,1,n);
        for (int i=1;i<=n;i++)
        {
            int l=1,r=i-1,mid,pos=i;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if (query(1,1,n,mid,i)<m) pos=mid,r=mid-1;
                else l=mid+1;
            }
            ans+=i-pos;
        }
        printf("%lld",ans);
        return 0;
    }

    T3 旅行

    题目大意:给定一棵含有$n$个结点的树,每条边有边权$p$表示$u,v$有$p$的概率连到一起。每个结点有$a_i,d_i$,表示会对与其距离不超过$d_i$结点的权值加上$a_i$。$q$次询问,每次询问点$x$所在连通块点权和的平方的期望。

    首先可以用树上差分或栈来求出每个结点的权值。然后我们先考虑怎么暴力对每个询问求出答案。设$g_i$表示以$i$为根的子树内和的平方的期望,$h_i$表示以$i$为根的子树内的和的期望(类似于OSU的做法)。根据期望的线性可加性,我们有:

    $E((a+b)^2)=E(a^2)+E(b^2)+E(2ab)$

    带入概率$p$得到:

    $E((a+b)^2)=p(a+b)^2+(1-p)a^2=a^2+p(2ab+b^2)$

    于是我们可以维护子树内的信息然后转移到父亲。最后根节点的$g$就是我们想要的答案。时间复杂度$O(nq)$。

    优化这一过程可以考虑换根DP。我们还可以维护$fg,fh$表示子树外的二次方期望和一次方期望。显然$fg$和$fh$只能从它的父亲和兄弟转移过来。然后就是一些换根DP的常规操作了。

    时间复杂度$O(n+q)$,空间复杂度$O(n)$。

    代码:

    #include<cstdio>
    #include<iostream>
    #define int long long
    using namespace std;
    const int N=200005;
    const int p=998244353;
    int a[N],d[N],f[N],g[N],h[N],fg[N],fh[N],tmp[N],vtmp[N];
    int st[N],ans[N],head[N],cnt,top,n,q;
    struct node{
        int next,to,dis;
    }edge[N*2];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void add(int from,int to,int dis)
    {
        edge[++cnt]=(node){head[from],to,dis};
        head[from]=cnt;
    }
    inline void dfs_f(int now,int fa)
    {
        f[now]=a[now];
        if (top>d[now]) (f[st[top-d[now]]]+=p-a[now])%=p;
        st[++top]=now;
        for (int i=head[now];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if (to==fa) continue;
            dfs_f(to,now);
            f[now]=(f[now]+f[to])%p;
        }
        st[top--]=0;
    }
    inline void dfs_g(int now,int fa)
    {
        g[now]=f[now]*f[now]%p,h[now]=f[now];
        for (int i=head[now];i;i=edge[i].next)
        {
            int to=edge[i].to,dis=edge[i].dis;
            if (to==fa) continue;
            dfs_g(to,now);
            g[now]=(g[now]+dis*(g[to]+2*h[now]*h[to]%p)%p)%p;
            h[now]=(h[now]+dis*h[to]%p)%p;
        }
    }
    inline void dfs_dp(int now,int fa,int dist)
    {
        ans[now]=(g[now]+dist*(fg[now]+2*fh[now]*h[now]%p)%p)%p;
        int g1=(f[now]*f[now]%p+dist*(fg[now]+2*fh[now]*f[now]%p))%p,h1=(fh[now]*dist+f[now])%p;
        tmp[0]=0;
        for (int i=head[now];i;i=edge[i].next)
        {
            int to=edge[i].to,dis=edge[i].dis;
            if (to==fa) continue;
            fg[to]=g1,fh[to]=h1;
            g1=(g1+dis*(g[to]+2*h[to]*h1%p)%p)%p;
            h1=(h1+dis*h[to]%p)%p;
            tmp[++tmp[0]]=to;
            vtmp[tmp[0]]=dis;
        }
        g1=0,h1=0;
        for (int i=tmp[0];i>=1;i--)
        {
            int to=tmp[i],dis=vtmp[i];
            fg[to]=(fg[to]+g1+2*fh[to]*h1%p)%p;
            fh[to]=(fh[to]+h1)%p;
            g1=(g1+dis*(g[to]+2*h1*h[to]%p)%p)%p;
            h1=(h1+dis*h[to]%p)%p;
        }
        for (int i=head[now];i;i=edge[i].next)
            if (edge[i].to!=fa) 
                dfs_dp(edge[i].to,now,edge[i].dis);
    }
    signed main()
    {
        n=read();
        for (int i=1;i<=n;i++)
            a[i]=read(),d[i]=read();
        for (int i=1;i<n;i++)
        {
            int u=read(),v=read(),d=read();
            add(u,v,d);add(v,u,d);
        }
        dfs_f(1,0);
        dfs_g(1,0);
        dfs_dp(1,0,0);
        q=read();
        while(q--) printf("%lld
    ",ans[read()]);
        return 0;
    }

    T4 平衡树

    题目大意:给定一棵二叉树每个点有点权,有三种操作:1.左旋;2.右旋;3.定义一个结点的力量值为其子树权值和,求子树内力量值之积。

    注意到平衡树的中序遍历是不变的,而左/右旋只会影响两个结点$x,y$,所以我们可以考虑使用线段树来维护其中序遍历。左右旋为单点修改,查询为区间积。

    另一种做法是LCT。左右旋:link/cut;更改子树和:单点修改;询问子树积:子树询问。可以在LCT上维护轻儿子,轻重链切换的时候顺便维护,这样就能进行子树询问了。但是这种方法码量大,而且我也不太会写QAQ……

    时间复杂度$O(nlog n)$。

    代码:

    #include<cstdio>
    #include<iostream>
    #define int long long
    using namespace std;
    const int N=200005;
    const int p=1e9+7;
    int ch[N][2],fa[N],L[N],R[N],dfn[N],tot,n,q,rt;
    int sum[N<<2],val[N],a[N],wt[N]; 
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline int get(int x){
        return ch[fa[x]][1]==x;
    }
    inline void pushup(int x){
        val[x]=(val[ch[x][0]]+val[ch[x][1]]+a[x])%p;
        L[x]=ch[x][0]?L[ch[x][0]]:dfn[x];
        R[x]=ch[x][1]?R[ch[x][1]]:dfn[x]; 
    }
    inline void rotate(int x)
    {
        int y=fa[x],z=fa[y],s=get(x);
        ch[z][get(y)]=x,fa[x]=z;
        ch[y][s]=ch[x][s^1],fa[ch[x][s^1]]=y;
        ch[x][s^1]=y,fa[y]=x;
        pushup(y);pushup(x);
    }
    inline void dfs(int x)
    {
        L[x]=tot+1;
        if (ch[x][0]) dfs(ch[x][0]);
        dfn[x]=++tot;
        if (ch[x][1]) dfs(ch[x][1]);
        R[x]=tot;
        val[x]=(val[ch[x][0]]+val[ch[x][1]]+a[x])%p;
        wt[dfn[x]]=val[x];
    }
    inline void build(int index,int l,int r)
    {
        if (l==r){
            sum[index]=wt[l];
            return;
        }
        int mid=(l+r)>>1;
        build(index*2,l,mid);build(index*2+1,mid+1,r);
        sum[index]=sum[index*2]*sum[index*2+1]%p;
    }
    inline void update(int index,int l,int r,int pos,int k)
    {
        if (l==r){
            sum[index]=k;
            return;
        }
        int mid=(l+r)>>1;
        if (pos<=mid) update(index*2,l,mid,pos,k);
        else update(index*2+1,mid+1,r,pos,k);
        sum[index]=sum[index*2]*sum[index*2+1]%p;
    }
    inline int query(int index,int l,int r,int ql,int qr)
    {
        if (ql<=l&&r<=qr) return sum[index];
        int mid=(l+r)>>1;
        if (qr<=mid) return query(index*2,l,mid,ql,qr)%p;
        else if (ql>mid) return query(index*2+1,mid+1,r,ql,qr)%p;
        else return query(index*2,l,mid,ql,qr)%p*query(index*2+1,mid+1,r,ql,qr)%p;
    }
    signed main()
    {
        n=read();q=read();
        for (int i=1;i<=n;i++)
        {
            a[i]=read(),ch[i][0]=read(),ch[i][1]=read();
            if (ch[i][0]) fa[ch[i][0]]=i;
            if (ch[i][1]) fa[ch[i][1]]=i;
        }
        for (int i=1;i<=n;i++)
            if (!fa[i]) rt=i;
        dfs(rt);
        build(1,1,n);
        while(q--)
        {
            int opt=read(),x=read();
            if (opt==0&&ch[x][0])
            {
                rotate(ch[x][0]);
                update(1,1,n,dfn[x],val[x]);
                update(1,1,n,dfn[fa[x]],val[fa[x]]);
            }
            if (opt==1&&ch[x][1])
            {
                rotate(ch[x][1]);
                update(1,1,n,dfn[x],val[x]);
                update(1,1,n,dfn[fa[x]],val[fa[x]]);
            }
            if (opt==2){
                printf("%lld
    ",query(1,1,n,L[x],R[x]));
            }
        }
        return 0;
    }
  • 相关阅读:
    [转]Navicat Premium 12试用期的破解方法
    Redis禁用持久化功能的设置
    阿里云ECS安装的redis服务器,用java代码去连接报错。
    关于Jedis连接Linux上的redis出现 DENIED Redis is running in protected mode问题的解决方案
    修改了jdk在环境变量中的路径怎么cmd中的jdk版本没有变
    阿里云上部署tomcat启动后,通过http不能访问
    【终结篇】不要再问我程序员该如何提高了……
    我是怎么把一个项目带崩的
    eterm和easyfare的官网地址
    java UTC时间和local时间相互转换
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13893380.html
Copyright © 2011-2022 走看看