前言:本人发现做倍增的题完全是无从下手,所以补了之前两道倍增的题,现来讲讲思路。
专题:用倍增优化前缀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][j−1]][j−1]
再写一个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();
/*for(int i=1;i<=n;i++) {
for(int j=0;j<=2;j++) {
printf("fa[%d][%d]=%d\n",i,j,fa[i][j]);
}
}*/
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++;
}
//printf("%d\n",u);
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);
}
}
//if(cost[u]>=cost[y]) ans++;
printf("%d\n",ans);
}
}
B.国旗计划
本题思路比较杂,是一个环形的区间覆盖问题,而且要解决多个初始点。但主要分为4步:
- 环破链。对枚举的初始区间[l,r],可看作覆盖区间[l,l+m]。而[l,r]会多分裂出来一个区间[l+m,r+m],用于第二圈使用。
- 预处理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;
}
- 求出f[][]数组。直接用倍增板子。
- 根据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;
//printf("%d\n",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];
}
}
/*for(int i=1;i<=cnt;i++) {
for(int j=0;j<=2;j++) {
printf("f[%d][%d]=%d\n",i,j,f[i][j]);
}
}*/
}
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);
/*for(int i=1;i<=cnt;i++) {
printf("%d %d\n",s[i].l,s[i].r);
}*/
work();
for(int i=1;i<=cnt;i++) {
if(s[i].id>n) continue;
//printf("%d %d\n",s[i].l,s[i].r);
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;
//printf("%d\n",p);
ans+=pow(2,j);
t=p;
}
P[s[i].id]=ans+1;
}
for(int i=1;i<=n;i++) printf("%lld ",P[i]);
}
代码真丑