【XSY3326】米缸(时间复杂度均衡,线段树,基环树,倍增)

本文探讨了一种优化时间复杂度的方法,针对一棵基环树上的操作,通过记录第一列节点信息来降低修改和查询的时间复杂度。提出暴力重构和使用线段树两种策略,分别达到O(n+m)和O(nlogm)的修改时间复杂度,以及O(m)的查询时间复杂度。此外,还提到了使用倍增技巧的解决方案,虽然码量小但时间复杂度稍高。

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

时间复杂度的均衡。

先考虑暴力的想法:显然这是一棵基环树,那么我们每次修改时暴力 O ( n m ) O(nm) O(nm) 重构基环树,然后询问的时候就能 O ( 1 ) O(1) O(1) 查询。时间复杂度 O ( n m q ) O(nmq) O(nmq)

考虑均衡时间复杂度,我们考虑只记录第一列的每个点 i i i m m m 步之后会绕回第一列的哪个点,设为 F i F_i Fi。那么这棵基环树的大小就是 n n n 了。然后询问的时候只需要先 O ( m ) O(m) O(m) 暴力走到第一列再直接 O ( 1 ) O(1) O(1) 在基环树上走然后再 O ( m ) O(m) O(m) 走完剩下的步数。

思考修改也能不能同样降复杂度。

答案是可行的。假设修改了点 ( x , y ) (x,y) (x,y) 的值,假设 ( x , y ) (x,y) (x,y) 往后走会走到第一列的第 t t t 个位置。我们可以从第 x x x 列往前推当前列的哪些位置往后走所到达的第一列的位置被更改了(显然若被更改只会被更改为 t t t),发现这始终是一个区间:假设第 i + 1 i+1 i+1 列是区间 [ l , r ] [l,r] [l,r] 的位置被改为了 t t t,那么当前第 i i i 列的 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r1] 的位置肯定也被更改为 t t t,而位置 l − 1 , l , r , r + 1 l-1,l,r,r+1 l1,l,r,r+1 都有可能被更改为 t t t,这四个位置直接暴力讨论即可。而且不可能存在 l − 1 l-1 l1 被改为了 t t t l l l 不会改的情况,于是这始终是一个区间。最后推到第一列即为要更改的 F i F_i Fi。然后我们再暴力 O ( n ) O(n) O(n) 重构基环树。

修改时间复杂度 O ( n + m ) O(n+m) O(n+m),询问时间复杂度 O ( m ) O(m) O(m)

修改降复杂度的做法有点难看出来,下面有一个更加暴力的做法:

审视修改时我们要解决的问题:修改某一个位置 ( x , y ) (x,y) (x,y) 的值,然后更新 F i F_i Fi,即求第一列每个点走完一圈后会回到第一列的哪个点。

直接上线段树。用线段树维护每一列每个点往后走一步会走到下一列哪个点,即线段树每个节点存一个大小为 n n n 的数组,然后合并的时候暴力 O ( n ) O(n) O(n) 合并。

那么在线段树中修改第 x − 1 x-1 x1 列,然后再更新 F i F_i Fi 即可。

修改时间复杂度 O ( n log ⁡ m ) O(n\log m) O(nlogm),询问时间复杂度 O ( m ) O(m) O(m)

然后你不想写基环树的话可以直接用倍增,修改时间复杂度 O ( n ( log ⁡ m + log ⁡ k ) ) O(n(\log m+\log k)) O(n(logm+logk)),询问时间复杂度 O ( m + log ⁡ k ) O(m+\log k) O(m+logk)

写的是最后一种码量最小但时间复杂度最大做法:

#include<bits/stdc++.h>

#define N 2010
#define LN 31

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,m,q,a[N][N],f[N][3];
int bit[LN],F[N][LN];
int nxt[N<<2][N];

int nxtx(int x,int y)
{
	int nxty=y%m+1;
	int maxn=0,maxx;
	for(int i=0;i<3;i++)
	{
		if(a[f[x][i]][nxty]>maxn)
		{
			maxn=a[f[x][i]][nxty];
			maxx=f[x][i];
		}
	}
	return maxx;
}

void up(int k)
{
	for(int i=1;i<=n;i++)
		nxt[k][i]=nxt[k<<1|1][nxt[k<<1][i]];
}

void build(int k,int l,int r)
{
	if(l==r)
	{
		for(int i=1;i<=n;i++)
			nxt[k][i]=nxtx(i,l);
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	up(k);
}

void update(int k,int l,int r,int x)
{
	if(l==r)
	{
		for(int i=1;i<=n;i++)
			nxt[k][i]=nxtx(i,l);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(k<<1,l,mid,x);
	else update(k<<1|1,mid+1,r,x);
	up(k);
}

void getF()
{
	for(int i=1;i<=n;i++)
		F[i][0]=nxt[1][i];
	for(int j=1;j<=30;j++)
		for(int i=1;i<=n;i++)
			F[i][j]=F[F[i][j-1]][j-1];
}

int main()
{
	bit[0]=1;
	for(int i=1;i<=30;i++) bit[i]=bit[i-1]<<1;
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			a[i][j]=read();
	for(int i=1;i<=n;i++)
		f[i][0]=(i-2+n)%n+1,f[i][1]=i,f[i][2]=i%n+1;
	build(1,1,m);
	getF();
	q=read();
	int nx=1,ny=1;
	while(q--)
	{
		char opt[10];
		scanf("%s",opt);
		if(opt[0]=='m')
		{
			int k=read();
			while(k>0&&ny!=1)
			{
				nx=nxtx(nx,ny);
				k--,ny=ny%m+1;
			}
			if(!k)
			{
				printf("%d %d\n",nx,ny);
				continue;
			}
			int div=k/m;
			k-=m*div;
			for(int i=30;i>=0;i--)
			{
				if(div-bit[i]>=0)
				{
					nx=F[nx][i];
					div-=bit[i];
				}
			}
			while(k>0)
			{
				nx=nxtx(nx,ny);
				k--,ny=ny%m+1;
			}
			printf("%d %d\n",nx,ny);
		}
		else
		{
			int x=read(),y=read();
			a[x][y]=read();
			update(1,1,m,(y-2+m)%m+1);
			getF();
		}
	}
	return 0;
}
/*
4 4
1 2 9 3
3 5 4 8
4 3 2 7
5 8 1 6
4
move 1
move 1
change 1 4 100
move 1
*/
/*
3 4
10 20 30 40
50 60 70 80
90 93 95 99
3
move 4	
change 2 1 100
move 4
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值