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; }