10月28日考试 题解(贪心+二分+树形DP+期望+线段树)

T1 饥饿的小鸟

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

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

我的思路就是每次让鸟尽可能往远飞,然后直接递推转移即可。正确性显然。复杂度$O(nl)$,然而因为$a_i\leq 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(n\log^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\n",ans[read()]);
    return 0;
}

T4 平衡树

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

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

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

时间复杂度$O(n\log 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\n",query(1,1,n,L[x],R[x]));
        }
    }
    return 0;
}

 

posted @ 2020-10-28 20:53  我亦如此向往  阅读(176)  评论(0编辑  收藏  举报