多校A层冲刺NOIP2024模拟赛10
多校A层冲刺NOIP2024模拟赛10
\(T1\) A. 岛屿 \(5/0/0pts\)
-
考虑下 \(x,y\) 的实际意义。
- \(x\) 表示初始时端点同为蓝/红色点连边的数量。
- \(y\) 表示初始时端点不同为蓝/红色间连边的数量。
-
因为最终每个点的度数都为 \(2\) ,所以每个连通块都恰好是一个大小至少为 \(2\) 的环。
-
加边的过程中,途中一定被分成了若干条链和若干个环,且端点同为红色和端点同为蓝色的链的数量是相等的。
-
设 \(f_{a,b}\) 表示初始时有 \(a\) 条红红链, \(a\) 条蓝蓝链和 \(b\) 条红蓝链(“红”“蓝”均指链端点颜色)的图等概率连接红蓝边后的期望连通块(仅计算环)数。
-
以第一条边为例,大力分讨。
- 红红链和蓝蓝链连接,得到红蓝链,连通块数为 \(f_{a-1,b+1}\) 。
- 红红链和红蓝链连接,得到红红链,连通块数为 \(f_{a,b-1}\) 。
- 蓝蓝链和红蓝链连接,得到蓝蓝链,连通块数为 \(f_{a,b-1}\) 。
- 红蓝链和红蓝链连接,得到红蓝链,连通块数为 \(f_{a,b-1}/f_{a,b-1}+1\) 。
- 前者表示两条不同的红蓝链之间连边,后者表示同一条红蓝链首尾相连。
-
等概率随机等价于按照某个固定顺序给每个红色端点独立随机匹配一个蓝色端点,不妨优先选择红蓝链的端点匹配其他链(剩下的情况可以通过重新赋予编号重排得到),从而简化状态间的转移。
-
状态转移方程为 \(f_{a,b}=\begin{cases} \frac{1}{2a+b} \times (f_{a,b-1}+1)+\frac{2a+b-1}{2a+b} \times f_{a,b-1}=\frac{1}{2a+1}+f_{a,b-1} & b>0 \\ f_{a-1,b+1}=f_{a-1,1}=\frac{1}{2a-1}+f_{a-1,0} & b=0 \end{cases}\) 。
-
边界为 \(f_{0,0}=0\) ,简化得到 \(f_{x,y}=\sum\limits_{b=1}^{y}\frac{1}{2x+b}+\sum\limits_{a=1}^{x}\frac{1}{2a-1}\) 即为所求。
点击查看代码
int main() { freopen("island.in","r",stdin); freopen("island.out","w",stdout); int x,y,i; double ans=0; cin>>x>>y; for(i=1;i<=y;i++) { ans+=1.0/(2*x+i); } for(i=1;i<=x;i++) { ans+=1.0/(2*i-1); } printf("%.12lf\n",ans); fclose(stdin); fclose(stdout); return 0; }
\(T2\) B. 最短路 \(10/20/20pts\)
-
部分分
- \(20 \%\) :暴力删边求最短路。
-
正解
- 设当前要求解的节点为 \(x\) ,显然由两部分节点更新而来。
- 一部分是 \(\min\limits_{y \ne fa_{x} \land y \notin Subtree(x),(x,y) \in E}\{ dis_{y}+w_{x,y} \}\) ;另一部分是 \(\min\limits_{y \in Subtree(x),z \notin Subtree(y)} \{ dis_{z}+w_{z,y}+w_{x,y} \}\) 。
- 前者直接枚举边即可,难点在于后者由多个变量组成。
- 不妨把路径拆开,有 \(1 \to z \to y \to x\) 的距离等于 \(1 \to z \to y \to x \to 1\) 的距离减去 \(1 \to x\) 的距离,将 \(dis_{z}+w_{x,y}+dis_{y}\) 扔进线段树里维护即可。
- 最终 \(DFS\) 一遍从下往上线段树合并即可。
点击查看代码
struct node { int nxt,to,w,id; }e[400010]; int head[100010],vis[200010],dfn[100010],out[100010],w[100010],cnt=0,tot=0; ll dis[100010],ans[100010]; pair<int,int>pre[100010]; vector<pair<int,int> >E[100010]; struct SMT { int root[100010],rt_sum=0; stack<int>s; struct SegmentTree { int ls,rs; ll minn; }tree[100010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) void init() { tree[0].minn=0x3f3f3f3f3f3f3f3f; } int build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=0; tree[rt_sum].minn=0x3f3f3f3f3f3f3f3f; return rt_sum; } void pushup(int rt) { tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn); } void update(int &rt,int l,int r,int pos,ll val) { rt=(rt==0)?build_rt():rt; if(l==r) { tree[rt].minn=min(tree[rt].minn,val); return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,val); } else { update(rson(rt),mid+1,r,pos,val); } pushup(rt); } int merge(int rt1,int rt2,int l,int r) { if(rt1==0||rt2==0) { return rt1+rt2; } if(l==r) { tree[rt1].minn=min(tree[rt1].minn,tree[rt2].minn); return rt1; } int mid=(l+r)/2; lson(rt1)=merge(lson(rt1),lson(rt2),l,mid); rson(rt1)=merge(rson(rt1),rson(rt2),mid+1,r); pushup(rt1); return rt1; } ll query(int rt,int l,int r,int x,int y) { if(rt==0) { return 0x3f3f3f3f3f3f3f3f; } if(x<=l&&r<=y) { return tree[rt].minn; } int mid=(l+r)/2; ll ans=0x3f3f3f3f3f3f3f3f; if(x<=mid) { ans=min(ans,query(lson(rt),l,mid,x,y)); } if(y>mid) { ans=min(ans,query(rson(rt),mid+1,r,x,y)); } return ans; } }T; void add(int u,int v,int w,int id) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; e[cnt].id=id; head[u]=cnt; } void dijsktra(int s) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pair<int,int> >q; dis[s]=0; q.push(make_pair(-dis[s],s)); while(q.empty()==0) { int x=q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; pre[e[i].to]=make_pair(x,e[i].id); q.push(make_pair(-dis[e[i].to],e[i].to)); } } } } } void dfs1(int x) { tot++; dfn[x]=tot; for(int i=0;i<E[x].size();i++) { dfs1(E[x][i].first); } out[x]=tot; } void dfs2(int x,int dep,int n) { for(int i=0;i<E[x].size();i++) { dfs2(E[x][i].first,dep+E[x][i].second,n); T.root[x]=T.merge(T.root[x],T.root[E[x][i].first],1,n); } if(x!=1) { if(dfn[x]-1>=1) { ans[x]=min(ans[x],T.query(T.root[x],1,n,1,dfn[x]-1)-dep); } if(out[x]+1<=n) { ans[x]=min(ans[x],T.query(T.root[x],1,n,out[x]+1,n)-dep); } for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].id]==0&&(!(dfn[x]<=dfn[e[i].to]&&dfn[e[i].to]<=out[x]))) { ans[x]=min(ans[x],dis[e[i].to]+e[i].w); T.update(T.root[x],1,n,dfn[e[i].to],dis[e[i].to]+e[i].w+dep); } } } } int main() { freopen("path.in","r",stdin); freopen("path.out","w",stdout); int n,m,u,v,i; scanf("%d%d",&n,&m); for(i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w[i]); add(u,v,w[i],i); add(v,u,w[i],i); } dijsktra(1); memset(vis,0,sizeof(vis)); for(i=2;i<=n;i++) { E[pre[i].first].push_back(make_pair(i,w[pre[i].second])); vis[pre[i].second]=1; } memset(ans,0x3f,sizeof(ans)); dfs1(1); T.init(); dfs2(1,0,n); for(i=2;i<=n;i++) { ans[i]=(ans[i]>=1e12)?-1:ans[i]; printf("%lld\n",ans[i]); } fclose(stdin); fclose(stdout); return 0; }
\(T3\) C. 列表 \(5/5/5pts\)
- 部分分
- 子任务 \(4\) :一直选某个方向的数即可,故 \(n+1\) 即为所求。
- 正解
-
直接挂官方题解了。
-
\(T4\) D. 种植 \(5/0/0pts\)
-
部分分
- \(35pts\) :农作物必须斜着种满一列,故 \(n+m-1\) 即为所求。
-
正解
- 直接挂官方题解了。
总结
- \(T2\) 没想到对于中转点在子树内部的怎么处理。
- \(T3\) 误认为对于每种删数方案都是合法的。
- \(T4\)
- 爆搜不知道哪里写假了,挂了 \(10pts\) 。
- 没有被毁坏的格子的做法在 \(n,m \le 5\) 的数据和爆搜拍上了,没敢往上交。
后记
- 初始时仅下发了 \(T2\) 的大样例;后来补上了 \(T1,T3,T4\) 的大样例;然后又被通知 \(T2\) 大样例换了,但比对后仅发现 \(n,m\) 间的空格改成了回车。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18490298,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。