【题解】倍增两题

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

专题:用倍增优化前缀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();
	/*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步:

  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;
		//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]);
}

代码真丑

猜你喜欢

转载自blog.csdn.net/cqbzlydd/article/details/108269994