【倍增专题】

【map】P4889 kls与flag

可以发现这道题求的是

assume j>i

j−i=h[i]+h[j]

j−i=abs(h[i]=h[j])

的对数。

那么显然,因为高度大于0,所以一个数对不可能同时满足两条式子,所以可以分开算

那么进行分类讨论:

  • (1) j−i=hi+hj -->hi+i=j−hj,那么开一个map,存下所有值的个数,每次都更新答案
  • (2) j−i=hj−hi, -->j−hj=i−hi,同(1)
  • (3) j−i=hi−hj, -->hi+i=hj+j,同(1)

那么如何保证j>i呢?倒序不就好了吗!

const int N=2e5+10;
int h[N];
int n,m;
map<int,int>mp;
#define big long long
big ans;

int main(){
    rd(n),rd(m);
    rep(i,1,n)rd(h[i]);
    //情况1
    dwn(i,n,1){
        ans+=(big)mp[h[i]+i];
        mp[i-h[i]]++;
    }
    mp.clear();
    //情况3
    dwn(i,n,1){
        ans+=(big)mp[i+h[i]];
        mp[h[i]+i]++;
    }
    mp.clear();
    //情况2
    dwn(i,n,1){
        ans+=(big)mp[i-h[i]];
        mp[i-h[i]]++;
    }
    printf("%lld",ans);
    return 0;
}

【倍增】P1613 跑路

先用一个数组预处理出可以一步到达的点对,再上floyd(n<=50不上floyd上什么)

BTW:部分盆友的智商堪忧,maxlongint 就是maxint 啊,你们开什么64,明明31就行了。​

const int N=60;
int n,m;
int dis[N][N];
bool G[N][N][64+5];

inline void prework(){
	rep(logn,1,64)
		rep(k,1,n)
			rep(i,1,n)
				rep(j,1,n)
					if(G[i][k][logn-1] && G[k][j][logn-1]){
						G[i][j][logn]=1;
						dis[i][j]=1;
					}
}

inline void floyd(){
	rep(k,1,n)
		rep(i,1,n)
			rep(j,1,n)
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}

int main(){
	#ifdef WIN32
	freopen("a.txt","r",stdin);
	#endif
	rd(n),rd(m);
	mem(dis,0x3f);
	while(m--){
		int u,v;rd(u),rd(v);
		G[u][v][0]=1;
		dis[u][v]=1;
	}
	prework();
	floyd();
	printf("%d",dis[1][n]);
	return 0;
}

【倍增】#137. 关联点

思考过程:

每个点网上跳val[i]步,到达的节点就是它产生贡献的节点,可是怎么判断是左边产生的贡献还是右边产生的贡献呢?很简单,我们只需条val[i]-1步,判断当前节点是它父节点的左儿子还是右儿子就好。

bebug:lson 和rson 不为0的时候才记录啊,我真是个智娃。

const int N=2e5+10;
int n,m;
int val[N],f[N][18+2],son[N][2],ansl[N],ansr[N];

struct edge{
	int v,next;
}e[N<<1];

int head[N],edge_num;
inline void adde(int u,int v){
	e[++edge_num].v=v;
	e[edge_num].next=head[u];
	head[u]=edge_num;
}

int main(){
	rd(n);
	rep(i,1,n){
		rd(val[i]);val[i]--;//开始就自减一
	}
	rep(i,1,n){
		int lson,rson;
		rd(lson),rd(rson);
		if(lson)f[lson][0]=i,son[i][0]=lson;
		if(rson)f[rson][0]=i,son[i][1]=rson;
	}
	rep(i,0,18)
		rep(u,1,n)
			f[u][i+1]=f[f[u][i]][i];
	rep(u,1,n){
		int x=u;//因为u要一直往上跳,而u还在循环里,不能直接改变,所以多开一个变量
		dwn(i,18,0)
			if((1<<i) & val[u])x=f[x][i];//注意这里是&val[u] 而不是一直在跳的val[x]
		int fa=f[x][0];
		if(son[fa][0]==x)ansl[fa]++;
		else ansr[fa]++;
	}
	rep(i,1,n)
		printf("%d %d\n",ansl[i],ansr[i]);
	return 0;
}

【倍增】P1967 货车运输

SOL

首先便是想到了Floyd的暴力方法,状态转移方程也不难推出:w[i] [j]=min(w[i] [j], w[i] [k]+w[k] [j]);但是n^ 3次方时间复杂度和n^2的空间复杂度是显然不可取的。

于是我们思考,可以发现有一些权值较小的边是不会被走过的。正如样例中的第三条边,就算有其他的很多条边,这条边无论如何也是不会被走过的。于是我们想到了可以将图中这样的边去掉,按照这个思路我们便想到了构造最大生成树,将其余的边去除。

得到了这样一个树之后,我们便考虑如何求出两个节点之间最小边权的最大值(即为题中的最大载重),因为这两点之间的路径是唯一的,我们只需要找出这条路径便可以得到答案。我们可以通过LCA来做到这一点,我求LCA的方法是先从每一个根节点进行搜索,求出节点深度等信息,然后利用这些信息进行树上倍增。

于是我们可以得出大体思路:首先重新建图,构造出最大生成树,然后在最大生成树上求LCA来回答询问。

const int N=1e4+10,M=5e4+10;
int f[N][18],w[N][18],fa[N],deep[N];
int tt,n,m,q;

struct edge{
	int u,v,w;
	bool operator <(const edge &rhs)const{
		return w>rhs.w;
	}
}e[M<<1];

inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}

struct tree{//最大生成树
	int v,next,w;
}tree[N<<1];

int head[N],edge_num;
inline void adde(int u,int v,int w){
	tree[++edge_num].v=v;
	tree[edge_num].w=w;
	tree[edge_num].next=head[u];
	head[u]=edge_num;
}

inline void kruskal(){
	sort(e+1,e+m+1);
	rep(i,1,n)fa[i]=i;
	rep(i,1,m){
		int fx=find(e[i].u),fy=find(e[i].v);
		if(fx!=fy){
			fa[fx]=fy;
			adde(e[i].u,e[i].v,e[i].w);
			adde(e[i].v,e[i].u,e[i].w);
		}
	}
}

inline void dfs(int u,int dad,int val){
	w[u][0]=val;
	deep[u]=deep[dad]+1;
	f[u][0]=dad;
	rep(i,0,tt){
		f[u][i+1]=f[f[u][i]][i];
		w[u][i+1]=min(w[u][i],w[f[u][i]][i]);
	}
	for(int i=head[u];i;i=tree[i].next){
		int v=tree[i].v;
		if(v==dad)continue;
		dfs(v,u,tree[i].w);
	}
}

inline int lca(int u,int v){
	int res=INT_MAX;
	if(deep[u]<deep[v])swap(u,v);
	dwn(i,tt,0){
		if(deep[u]-(1<<i)>=deep[v]){
			res=min(res,w[u][i]);
			u=f[u][i];
		}
	}
	if(u==v)return res;
	dwn(i,tt,0){
		if(f[u][i]!=f[v][i]){
			res=min(res,min(w[u][i],w[v][i]));
			u=f[u][i],v=f[v][i];
		}
	}
	return min(res,min(w[u][0],w[v][0]));
}

int main(){
	mem(w,INT_MAX);
	rd(n),rd(m);
	tt=log2(n)+1;
	rep(i,1,m){
		rd(e[i].u),rd(e[i].v),rd(e[i].w);
	}
	kruskal();
	rep(i,1,n)//题上没有说是连通图,可能是森林
		if(i==fa[i])
			dfs(i,i,INT_MAX);
	rd(q);
	while(q--){
		int u,v;rd(u),rd(v);
		if(find(u)!=find(v))puts("-1");
		else printf("%d\n",lca(u,v));
	}
    return 0;
}
posted @ 2019-11-03 09:26  设计涉及社稷  阅读(145)  评论(0编辑  收藏  举报