2001: [Hnoi2010]City 城市建设

本文介绍了一种基于分治思想的最小生成树(MST)算法实现细节,该算法通过递归地将问题分解为更小的部分来寻找最优解。在每个阶段,算法通过修改边的权重来确定哪些边是必要的,哪些可以被删除。最终目的是找到包含所有顶点的最小权重边集合。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当分治到区间[l,r]时:

如果l==r,直接跑mst,结束。否则:

S1存初始边和[1,l)的边,S2存[l,r]的边。

先把S2中的边权全都改成inf,跑mst,S1中用不到的边即为无用边,直接删去。

再把S2中的边权全都改成-inf,跑mst,S1中仍被用到的边即为必需边,把这些边连接的两点用并查集搞在一起,并把这些边权和传到下一层。

对于分治的每一种深度,开一个边、一个点集,一个并查集,一坨询问。因为深度最多为logn、同种深度的区间不会同时运行,所以只需开logn个就够了,空间是不会爆的。

TMD把题意看错了,写了个只有加边的。。。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define sqr(x) ((x)*(x))
#define G getchar()
#define LL long long
#define pii pair<int,int>
#define mkp make_pair
#define X first
#define Y second
#define N 200005
#define NN 500005
#define inf 100000000
struct EDGE{int a,b,w;}qu[NN];
struct EDGE2{int a,b,w,wz;}ek[NN<<1];
struct node{
	EDGE e[NN<<1];
	int tot,poi[N],cnt,par[N],sz[N];
}Hz[17];
int n,m,Q,totk,park[N],szk[N];
LL ans[NN];
bool flg[NN<<1];
void read(int &x){
	char ch=G;
	while(ch<48||ch>57)ch=G;
	for(x=0;ch>47&&ch<58;ch=G)x=x*10+ch-48; 
}
bool cmp(EDGE2 x,EDGE2 y){return x.w<y.w;}
int getpar(int *par0,int x){
	if(par0[x]!=x)par0[x]=getpar(par0,par0[x]);
	return par0[x];
}
void cmb(int *par0,int *sz0,int x,int y){
	x=getpar(par0,x);y=getpar(par0,y);
	if(sz0[x]<sz0[y])par0[x]=par0[y];
	else par0[y]=par0[x];
}
LL kruscal(int *poi0,int cnt0){
	int i,x,y,j;LL rtn=0;
	rep(i,1,cnt0)szk[park[i]=i]=1;
	rep(i,1,totk)flg[i]=0;
	sort(ek+1,ek+totk+1,cmp);
	for(i=j=1;j<cnt0;++i){
		x=getpar(park,ek[i].a);y=getpar(park,ek[i].b);
		if(x==y)continue;++j;
		flg[ek[i].wz]=1;rtn+=ek[i].w;
		cmb(park,szk,ek[i].a,ek[i].b);
	}
	return rtn;
}
void cdq(int l,int r,LL sum,int dep){
	EDGE *e0=Hz[dep].e;
	int &tot0=Hz[dep].tot,*poi0=Hz[dep].poi,&cnt0=Hz[dep].cnt,*par0=Hz[dep].par,*sz0=Hz[dep].sz;
	int i;
	rep(i,1,cnt0)sz0[par0[i]=i]=1;
	totk=tot0;
	rep(i,1,tot0)ek[i]=(EDGE2){e0[i].a,e0[i].b,e0[i].w,i};
	if(l==r){
		ek[++totk]=(EDGE2){qu[l].a,qu[l].b,qu[l].w,totk};
		ans[l]=kruscal(poi0,cnt0)+sum;
		return;
	}
	rep(i,l,r)ek[++totk]=(EDGE2){qu[i].a,qu[i].b,inf,totk};
	kruscal(poi0,cnt0);
	totk=tot0;
	rep(i,1,tot0){
		ek[i]=(EDGE2){e0[i].a,e0[i].b,e0[i].w,i};
		if(!flg[i])e0[i].a=0;
	}
	rep(i,l,r)ek[++totk]=(EDGE2){qu[i].a,qu[i].b,-inf,totk};
	kruscal(poi0,cnt0);
	EDGE *e1=Hz[dep+1].e;
	int &tot1=Hz[dep+1].tot,*poi1=Hz[dep+1].poi,&cnt1=Hz[dep+1].cnt;
	int x,mid=l+r>>1;
	rep(i,1,tot0){
		if(!e0[i].a)continue;
		if(flg[i]){
			cmb(par0,sz0,e0[i].a,e0[i].b);
			e0[i].a=0;sum+=e0[i].w;
		}
	}
	rep(i,1,cnt0){
		x=poi0[i];
		if(getpar(par0,x)==x)poi1[++cnt1]=x;
	}
	rep(i,1,tot0)if(e0[i].a)
		e1[++tot1]=(EDGE){getpar(par0,e0[i].a),getpar(par0,e0[i].b),e0[i].w};
	rep(i,l,r){
		qu[i].a=getpar(par0,qu[i].a);
		qu[i].b=getpar(par0,qu[i].b);
	}
	cdq(l,mid,sum,dep+1);
	rep(i,l,mid)e1[++tot1]=qu[i];
	cdq(mid+1,r,sum,dep+1);
}
int main(){
	int i,x;
	read(n);read(m);read(Q);
	EDGE *e0=Hz[1].e;
	rep(i,1,m){
		read(e0[i].a);read(e0[i].b);read(e0[i].w);
	}
	Hz[1].tot=m;
	int *poi0=Hz[1].poi;
	rep(i,1,n)poi0[i]=i;
	Hz[1].cnt=n;
	rep(i,1,Q){
		read(x);read(qu[i].w);
		qu[i].a=e0[x].a;qu[i].b=e0[x].b;
	}
	cdq(1,Q,0LL,1);
	rep(i,1,Q)printf("%d\n",ans[i]);
	return 0;
}

简直脑子进水,下面的代码写了我一整天。不过我还真没见过这么难写的题。觉得这题比Claris loves painting 还难写得多。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define sqr(x) ((x)*(x))
#define G getchar()
#define LL long long
#define pii pair<int,int>
#define mkp make_pair
#define X first
#define Y second
#define N 20005
#define NN 50005
#define inf 100000000
struct EDGE{int a,b,w;};
struct EDGE2{int a,b,w,wz;}ek[NN<<1];
struct QUERY{int cur,w;};
struct node{
	EDGE e[NN<<1];QUERY qu[NN];
	int tot,poi[N],cnt,par[N],sz[N];
}Hz[20];
int n,m,Q,totk,park[N],szk[N],key[NN];
LL ans[NN];
bool flg[NN<<1];
void read(int &x){
	char ch=G;
	while(ch<48||ch>57)ch=G;
	for(x=0;ch>47&&ch<58;ch=G)x=x*10+ch-48; 
}
bool cmp(EDGE2 x,EDGE2 y){return x.w<y.w;}
int getpar(int *par0,int x){
	if(par0[x]!=x)par0[x]=getpar(par0,par0[x]);
	return par0[x];
}
void cmb(int *par0,int *sz0,int x,int y){
	x=getpar(par0,x);y=getpar(par0,y);
	if(sz0[x]<sz0[y])sz0[par0[x]=y]+=sz0[x];
	else sz0[par0[y]=x]+=sz0[y];
}
LL kruscal(int *poi0,int cnt0){
	int i,x,y,j;LL rtn=0;
	rep(i,1,cnt0)szk[park[poi0[i]]=poi0[i]]=1;
	rep(i,1,totk)flg[i]=0;
	sort(ek+1,ek+totk+1,cmp);
	for(i=j=1;j<cnt0;++i){
		x=getpar(park,ek[i].a);y=getpar(park,ek[i].b);
		if(x==y)continue;++j;
		flg[ek[i].wz]=1;rtn+=ek[i].w;
		cmb(park,szk,ek[i].a,ek[i].b);
	}
	return rtn;
}
void cdq(int l,int r,LL sum,int dep){
	EDGE *e0=Hz[dep].e;QUERY *qu0=Hz[dep].qu;
	int &tot0=Hz[dep].tot,*poi0=Hz[dep].poi,&cnt0=Hz[dep].cnt,*par0=Hz[dep].par,*sz0=Hz[dep].sz;
	int i;
	rep(i,1,cnt0)sz0[par0[poi0[i]]=poi0[i]]=1;
	totk=tot0;
	rep(i,1,tot0)ek[i]=(EDGE2){e0[i].a,e0[i].b,e0[i].w,i};
	if(l==r){
		ek[qu0[l].cur].w=qu0[l].w;
		ans[l]=kruscal(poi0,cnt0)+sum;
		return;
	}
	rep(i,l,r)
		ek[qu0[i].cur].w=inf;
	kruscal(poi0,cnt0);
	totk=tot0;
	rep(i,l,r)flg[qu0[i].cur]=1;
	rep(i,1,tot0){
		ek[i]=(EDGE2){e0[i].a,e0[i].b,e0[i].w,i};
		if(!flg[i])e0[i].a=0;
	}
	rep(i,l,r)ek[qu0[i].cur].w=-inf;
	kruscal(poi0,cnt0);
	EDGE *e1=Hz[dep+1].e;QUERY *qu1=Hz[dep+1].qu;
	int &tot1=Hz[dep+1].tot,*poi1=Hz[dep+1].poi,&cnt1=Hz[dep+1].cnt;
	int x,mid=l+r>>1;
	rep(i,l,r)flg[qu0[i].cur]=0;
	rep(i,1,tot0){
		if(!e0[i].a)continue;
		if(flg[i]){
			cmb(par0,sz0,e0[i].a,e0[i].b);
			e0[i].a=0;sum+=e0[i].w;
		}
	}
	cnt1=tot1=0;
	rep(i,1,cnt0){
		x=poi0[i];
		if(getpar(par0,x)==x)poi1[++cnt1]=x;
	}
	rep(i,1,tot0)if(e0[i].a)
		e1[key[i]=++tot1]=(EDGE){getpar(par0,e0[i].a),getpar(par0,e0[i].b),e0[i].w};
	rep(i,l,r)qu1[i]=(QUERY){key[qu0[i].cur],qu0[i].w};
	cdq(l,mid,sum,dep+1);
	tot1=0;
	rep(i,1,tot0)if(e0[i].a)
		e1[++tot1]=(EDGE){getpar(par0,e0[i].a),getpar(par0,e0[i].b),e0[i].w};
	rep(i,l,mid)e1[qu1[i].cur].w=qu1[i].w;
	cdq(mid+1,r,sum,dep+1);
}
int main(){
	int i;
	read(n);read(m);read(Q);
	EDGE *e0=Hz[1].e;QUERY *qu0=Hz[1].qu;
	rep(i,1,m){
		read(e0[i].a);read(e0[i].b);read(e0[i].w);
	}
	Hz[1].tot=m;
	int *poi0=Hz[1].poi;
	rep(i,1,n)poi0[i]=i;
	Hz[1].cnt=n;
	rep(i,1,Q){
		read(qu0[i].cur);read(qu0[i].w);
	}
	cdq(1,Q,0LL,1);
	rep(i,1,Q)printf("%lld\n",ans[i]);
	return 0;
}




<think>好的,我现在需要解决用户关于[HNOI2010]弹飞绵羊的问题,特别是分块方法的解题思路。首先,我需要回忆一下这个题目的具体内容以及分块算法的一般应用。 题目大意应该是每个装置有一个弹力系数,当绵羊到达某个位置时会被弹到后面的某个位置,直到弹飞为止。问题应该是要求动态维护弹飞所需的步数,或者支持修改某个位置的弹力系数,同时查询某个点弹飞所需的次数。 分块处理这种问题通常是将数据分成多个块,每个块维护一些预处理的信息,这样在查询和修改时可以降低时间复杂度。比如,对于每个位置i,可以预处理出跳出当前块需要的步数,以及跳出后的落点位置。这样查询时就可以逐块跳跃,减少计算量。 接下来,我需要具体思考分块如何在这里应用。每个块的大小通常取√n,这样总共有√n个块。对于每个位置i,在预处理阶段,需要计算从i出发,需要多少步才能跳出当前所在的块,并且记录跳出后的位置。这样,当查询某个点的弹飞步数时,只需要累加每个块的步数,直到弹飞为止。修改操作时,只需要更新所在块内的信息,因为块内的预处理信息可能被影响,但其他块不受影响。 例如,假设块大小为m,对于每个位置i,如果i + a[i]超出了当前块的范围,则step[i] = 1,next[i] = i + a[i]。否则,step[i] = step[i + a[i]] + 1,next[i] = next[i + a[i]]。这样预处理之后,查询时从当前位置开始,每次跳到next[i],并累加step[i],直到next[i]超过n,即弹飞。 修改操作时,比如修改位置k的弹力系数,那么需要从k所在块的起始位置开始,重新计算该块内所有位置的step和next。这是因为修改可能影响该块内其他位置的预处理结果。比如,如果某个位置j的next[j]原本指向k的位置,修改k的弹力系数会影响j的next和step,所以需要重新计算整个块的信息。 这样,每次查询的时间复杂度是O(√n),因为每个块最多跳一次,而块的数量是√n。修改操作的时间复杂度是O(√n),因为需要重新处理整个块,大小是√n。 需要注意的是,分块的具体实现需要确定块的大小,通常取√n,但有时根据实际情况调整可能会有更好的效果。另外,预处理每个块的step和next时,需要从块的末尾向前处理,因为后面的位置的处理结果可能影响前面的位置。 可能还需要考虑边界条件,比如弹力系数是否可能超过数组长度,或者弹飞的条件。比如,当i + a[i] >= n时,就算弹飞,此时step[i]=1,next[i]=n或某个超出范围的标记。 另外,在实现过程中,如何处理块的分界点,以及如何遍历每个块内的元素,都是需要注意的细节。例如,块的范围是左闭右开还是左闭右闭,需要统一处理,避免数组越界或者漏掉元素。 总结一下,解题步骤应该是: 1. 将整个数组分成若干块,块的大小约为√n。 2. 预处理每个块内的每个位置i的step[i]和next[i],处理顺序应该是从块的最后一个元素向前处理。 3. 查询时,从查询点开始,不断跳到next[i],并累加step[i],直到i超出数组范围,总步数即为答案。 4. 修改时,找到修改位置所在的块,重新预处理该块内的所有元素的step和next。 这样,分块方法能够将时间复杂度降到O(m√n),其中m是操作次数,适用于较大的数据规模,符合算法竞赛的要求。 在代码实现时,需要注意块的下标处理,比如块号从0开始还是1开始,块的大小是否需要调整以适应数组长度不能被整除的情况。此外,预处理函数需要正确计算每个块的起点和终点,并在块内逆序处理元素,以确保后面的元素先被处理,前面的元素可以正确引用后面的step和next值。 举个例子,假设数组长度n=10,块大小m=3,那么块0是0-2,块1是3-5,块2是6-8,块3是9。对于每个块内的元素,比如块0,从位置2开始处理到0。对于位置i=2,如果a[2]=4,则i+a[i]=6,超过块0的范围,所以step[2]=1,next[2]=6。然后处理i=1,假设a[1]=2,则i+a[i]=3,属于块1,所以step[1]=1,next[1]=3。接着处理i=0,假设a[0]=1,则i+a[i]=1,仍在块0内,所以step[0] = step[1] + 1 = 2,next[0] = next[1] =3。这样,当查询位置0时,会先跳到3,再跳到next[3]的位置,依此类推,直到弹飞。 因此,分块方法的关键在于预处理每个块内的跳跃信息,使得查询时可以快速跨块计算,而修改时只需要更新所在块的信息,从而将时间复杂度降低到可接受的范围。</think>### [HNOI2010]弹飞绵羊分块解法思路 #### 问题分析 题目要求维护一个包含$n$个弹簧的序列,每个弹簧有弹力系数$k_i$。当绵羊落在位置$i$时,会被弹到$i + k_i$的位置,直到弹飞(即位置$\geq n$)。需要支持两种操作: 1. **查询**:从位置$x$出发,弹多少次会被弹飞? 2. **修改**:将位置$x$的弹力系数改为$y$。 #### 分块思想 将序列分为$\sqrt{n}$个块,每个块大小约为$\sqrt{n}$。预处理每个位置的**跳跃步数**和**落点**,使得: - **块内跳跃信息**:对于每个位置$i$,记录跳出当前块所需的步数$step[i]$和最终落点$next[i]$。 - **查询优化**:每次查询只需逐块跳跃,时间复杂度$O(\sqrt{n})$。 - **修改优化**:修改仅影响当前块的信息,时间复杂度$O(\sqrt{n})$。 #### 预处理方法 1. **块划分**:将数组划分为$m = \lceil \sqrt{n} \rceil$个块,每个块大小为$m$。 2. **逆序处理**:从每个块的最后一个位置向前遍历: - 若$i + k_i$超出当前块,则$step[i] = 1$,$next[i] = i + k_i$。 - 若未超出,则继承下一个位置的步数和落点: $$step[i] = step[i + k_i] + 1, \quad next[i] = next[i + k_i]$$ #### 查询操作 从位置$x$开始,不断跳转到$next[x]$并累加$step[x]$,直到$next[x] \geq n$。总步数即为答案。 #### 修改操作 1. 找到位置$x$所在的块。 2. **重置块内信息**:从该块的最后一个位置重新逆序计算$step$和$next$。 #### 代码框架(Python示例) ```python import math class BlockSolution: def __init__(self, n, k): self.n = n self.k = k.copy() self.block_size = int(math.sqrt(n)) + 1 self.step = [0] * n self.next = [0] * n self.build_blocks() def build_blocks(self): # 分块预处理 for block_start in range(0, self.n, self.block_size): block_end = min(block_start + self.block_size, self.n) for i in range(block_end - 1, block_start - 1, -1): if i + self.k[i] >= block_end: # 跳出当前块 self.step[i] = 1 self.next[i] = i + self.k[i] else: # 依赖块内后续位置 self.step[i] = self.step[i + self.k[i]] + 1 self.next[i] = self.next[i + self.k[i]] def query(self, x): # 查询弹跳次数 res = 0 while x < self.n: res += self.step[x] x = self.next[x] return res def update(self, x, y): # 修改弹力系数并重建块 self.k[x] = y block_start = (x // self.block_size) * self.block_size block_end = min(block_start + self.block_size, self.n) for i in range(block_end - 1, block_start - 1, -1): if i + self.k[i] >= block_end: self.step[i] = 1 self.next[i] = i + self.k[i] else: self.step[i] = self.step[i + self.k[i]] + 1 self.next[i] = self.next[i + self.k[i]] ``` #### 复杂度分析 - **预处理**:$O(n)$ - **查询**:$O(\sqrt{n})$ - **修改**:$O(\sqrt{n})$ #### 应用场景 分块法适用于需要**动态维护跳跃路径**且**支持快速修改**的问题,例如弹飞绵羊、树状路径跳跃等[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值