【总结】倍增

前言:本人发现做倍增的题完全是无从下手,所以补了之前两道倍增的题,现来讲讲思路。

专题:用倍增优化前缀pre数组(类似于lca)

A.城市网络

分析:这道题没想到思路就很难做。
首先是一个树上倍增,这很明显。

思路一: f [ x ] [ y ] f[x][y] f[x][y]表示 x x x x x x 2 y 2^y 2y祖先的路径中的最大值。只要c>=f[x][y],就跳 2 y 2^y 2y步。直到不能跳为止,这个时候令ans++,x=fa[x].这样虽然能跳过不交易的点,但是假如每一个点都要交易,就与暴力无异了。

思路二: f [ x ] [ y ] f[x][y] f[x][y]表示 x x x 2 y 2^y 2y交易后的位置。这里初始价值为cost[x]。不难发现具有传递性,所以:
f [ y ] [ j ] = f [ f [ y ] [ j − 1 ] ] [ j − 1 ] f[y][j]=f[f[y][j-1]][j-1] f[y][j]=f[f[y][j1]][j1]
再写一个find函数,用于求对于任意价值y,从节点x开始,第一个cost[z]>y的点z。然后就可以利用上述f数组求解。注意用dep[]数组存深度,这是有根树的bfs的基本操作,就不讲了。时间复杂度O(nlogn)

#include<bits/stdc++.h> using namespace std; const int N=1e5+5; //用倍增优化前缀数组(类似于lca) void read(int &x) { int f=1;x=0;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();} x*=f; } int t,n,q,fa[N][20],dep[N],cost[N]; vector<int> son[N]; int find(int x,int y) { int idx=x; for(int i=t;i>=0;i--) { int z=fa[idx][i]; if(!z) continue; if(cost[z]<=y) { idx=z; } } if(cost[fa[idx][0]]>y) return fa[idx][0]; return 0; } void bfs() { queue<int> q; q.push(1); dep[1]=1; while(q.size()) { int x=q.front();q.pop(); for(int i=0;i<son[x].size();i++) { int y=son[x][i]; if(!dep[y]) { dep[y]=dep[x]+1; q.push(y); if(cost[y]<cost[x]) fa[y][0]=x; else { fa[y][0]=find(x,cost[y]); } for(int j=1;j<=t;j++) fa[y][j]=fa[fa[y][j-1]][j-1]; } } } } int main() { read(n),read(q); t=log(n)/log(2)+1; for(int i=1;i<=n;i++) read(cost[i]); for(int i=1;i<n;i++) { int x,y; read(x),read(y); son[x].push_back(y); son[y].push_back(x); } bfs(); while(q--) { int x,y,z,u,ans=0; read(x),read(y),read(z); if(cost[x]>z) u=x,ans++; else { u=find(x,z); if(dep[u]>=dep[y]) ans++; } if(u==0) { printf("0\n"); continue; } for(int i=t;i>=0;i--) { if(fa[u][i]&&dep[fa[u][i]]>=dep[y]) { u=fa[u][i]; ans+=pow(2,i); } } printf("%d\n",ans); } }

B.国旗计划

本题思路比较杂,是一个环形的区间覆盖问题,而且要解决多个初始点。但主要分为4步:

  1. 环破链。对枚举的初始区间[l,r],可看作覆盖区间[l,l+m]。而[l,r]会多分裂出来一个区间[l+m,r+m],用于第二圈使用。
  2. 预处理f[i][0]。换句话说,就是对于每个给定的区间[l,r],求出一个后继[L,R],使l<=L<=r&&R最大。可以先按l排序,这样r也是单调递增的(因为没有包含关系),这样从1到cnt,发现i会继承1-i-1的所有决策区间,所以j和idx不用重新赋值。时间复杂度降为O(n)
//优化后 int j=2,idx=0; for(int i=1;i<=cnt;i++) { //j=i+1,idx=0;(这里不必赋值j,因为r单调递增) while(j<=cnt&&s[j].l<=s[i].r) { if(s[j].r>s[idx].r) idx=j; j++; } if(idx<=i) f[i][0]=0; else f[i][0]=idx; }
  1. 求出f[][]数组。直接用倍增板子。
  2. 根据f[][]数组计算,若s[p].r超出了s[i].l+m,则说明跳大了。最后输出ans+1(因为最后还要选择一个区间,才能到达s[i].l+m,倍增跳的是没到达部分)。

总时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn).

#include<bits/stdc++.h> using namespace std; const int N=4e5+5; struct rap{ long long l,r,id; friend bool operator <(rap a,rap b) { return a.l<b.l; } }s[N]; long long cnt,n,m,f[N][20],P[N]; //bool vis[N]; void work() { int j=2,idx=0; for(int i=1;i<=cnt;i++) { while(j<=cnt&&s[j].l<=s[i].r) { if(s[j].r>s[idx].r) idx=j; j++; } if(idx<=i) f[i][0]=0; else f[i][0]=idx; } for(int j=1;j<20;j++) { for(int i=1;i<=cnt;i++) { if(!f[i][j-1]||s[f[i][j-1]].r>=s[i].l+m) continue; f[i][j]=f[f[i][j-1]][j-1]; } } } int main() { scanf("%lld%lld",&n,&m); cnt=n; for(int i=1;i<=n;i++) { scanf("%lld%lld",&s[i].l,&s[i].r); s[i].id=i; if(s[i].r<s[i].l) s[i].r+=m; s[++cnt]=(rap){s[i].l+m,s[i].r+m,cnt}; //注意无论是否超出2m,都要增加一个区间。 } sort(s+1,s+1+cnt); work(); for(int i=1;i<=cnt;i++) { if(s[i].id>n) continue; long long ans=1,t=i; for(int j=19;j>=0;j--) { long long p=f[t][j]; if(!p||s[p].r>=s[i].l+m) continue; ans+=pow(2,j); t=p; } P[s[i].id]=ans+1; } for(int i=1;i<=n;i++) printf("%lld ",P[i]); }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530400.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(7)  评论(0编辑  收藏  举报  
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示