CF914D题解

原题

CF914D Bash and a Tough Math Puzzle


思路概述

题意分析

给定一个数列 \(\{a\}\) 和两个操作。第一个操作输入数 \(1,l,r,x\) ,表示询问在区间 \([l,r]\) 内能否在最多改变一个数的情况下使得 \(gcd(a_l,a_{l+1}...a_{r})=x\) ;第二个操作输入 \(2,i,y\) 表示执行赋值操作 \(a_i=y\)

思路分析

区间查询+单点修改,一眼RMQ问题,直接套线段树板子即可。将权值线段树记录的数据从区间和改为区间最大公约数即可。但编码过程中坑点细节较多,需要在线段树模板上进行部分修改。


算法实现

最大公约数的计算

辗转相除法,不解释。算法流程如下:

while(x%y!=0)
{
	define temp
	temp<-x%y
	x<-y
	y<-temp
}
return y

关于建树

常规线段树做法,同样放代码。

inline void build(int p,int l,int r)
{
	e[p].l=l;e[p].r=r;
	if(l==r) cin >> e[p].data;
	else 
	{
		RI mid=(l+r)>>1;
		build(p<<1,l,mid);build(p<<1|1,mid+1,r);
		push_up(p);
	}
	return;
}

关于懒标记

因为本题只涉及单点值修改,没有区间覆盖操作,所以不需要传递懒标记,只需要每次单点修改后更新其所在所有区间的 \(gcd\) 即可。

修改与查询

每次修改时,将区间改为 \([i,i]\) ,然后套权值线段树模板即可。

查询操作有点复杂。本题要求查询区间 \([l,r]\) 内的数列能否在至多修改一个数的前提条件下满足最大公约数为 \(x\) ,所以我们直接查询区间最大公约数,若不是 \(x\) ,就说明区间内必然存在至少一个数 \(a_k\) 不能被 \(x\) 整除,那么继续向其左右子区间查询。当出现 \(l=r\) 并且 \(\frac{a_l}{x}∉N^*\) ,则说明此处有一个数需要被修改,最后统计需要被修改的节点总数就可以判定是否满足询问条件。

关于TLE的问题

由于本题线段树查询与常规权值线段树不同,没有使用懒标记,需要不断向子区间查询直到 \(l=r\) ,所以单次查询时间复杂度上界为 \(O(2n)\) ,明显不满足数据规模要求。

再次分析题意,发现题目根本没有要求查询区间内有多少数不能被 \(x\) 整除,所以只需要在查询值 \(ret>1\) 时直接跳出询问函数返回 \(ret=2\) 即可。


AC code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<set>
#include<ctime>
#define RI register int
using namespace std;
const int  maxn=5e5+10;
typedef struct
{
	int l,r;
	int data,sft;
}node;
node e[maxn<<2];
int T,n,m;
inline int gcd(int x,int y);
inline void push_up(int p);
inline void build(int p,int l,int r);
inline void change(int p,int l,int r,int x);
inline int query(int p,int l,int r,int x);
int main()
{
	ios::sync_with_stdio(false);cin.tie(0);
	cin >> n;build(1,1,n);
	cin >> m;
	for(RI opt,l,r,x;m;--m)
	{
		cin >> opt;
		if(opt==1)
		{
			cin >> l >> r >> x;
			puts((query(1,l,r,x)<=1)?"YES":"NO");
		}
		else
		{
			cin >> l >> x;r=l;
			change(1,l,r,x);
		}
	}
	return 0;
}
inline int gcd(int x,int y)
{
	while(x%y)
	{
		RI temp=x%y;
		x=y;y=temp;
	}
	return y;
}
inline void push_up(int p)
{
	/*根据最大公约数性质可知当前区间最大公约数等于两个子区间最大公约数的最大公约数*/
	e[p].data=gcd(e[p<<1].data,e[p<<1|1].data);
	return;
}
inline void build(int p,int l,int r)
{
	e[p].l=l;e[p].r=r;
	if(l==r) cin >> e[p].data;
	else 
	{
		RI mid=(l+r)>>1;
		build(p<<1,l,mid);build(p<<1|1,mid+1,r);
		push_up(p);
	}
	return;
}
inline void change(int p,int l,int r,int x)
{
	if(e[p].l>=l && e[p].r<=r) e[p].data=x;/*单点修改*/
	else
	{
		RI mid=(e[p].l+e[p].r)>>1;
		if(mid>=l) change(p<<1,l,r,x);
		if(mid<r) change(p<<1|1,l,r,x);
		push_up(p);
	}
	return;
}
inline int query(int p,int l,int r,int x)
{
	if(e[p].l==e[p].r) return !(!(e[p].data%x));/*当一个数无法整除 返回1*/
	else
	{
		RI mid=(e[p].l+e[p].r)>>1,ret=0;
		if(mid>=l && e[p<<1].data%x) ret+=query(p<<1,l,r,x);/*左子区间存在数不能整除*/
		if(ret>1) return ret;/*关键操作 出现1个以上数字不能整除直接返回 否则会超时*/
		if(mid<r && e[p<<1|1].data%x) ret+=query(p<<1|1,l,r,x);/*右子区间存在数不能整除*/
		return ret;
	}
}	puts("Marsel");	
	}
	else puts("Marsel");
	return 0;
}
posted @ 2022-05-21 08:46  UOB  阅读(98)  评论(0)    收藏  举报