CFdiv1+2-Bash and a Tough Math Puzzle-(线段树单点区间维护gcd+总结)

Bash and a Tough Math Puzzle

题意:
就是有一个n的数组,然后有m次操作,有两种,一种是让va[a] = b,一种是问你l到r这段区间的gcd是否接近x。接近x的定义是,要么等于x,要么你改变这段区间的一个数再等于x。

思考:
1.看到这题,首先想到了gcd的性质,gcd(a1,a2,a3) = gcd(a1,a2-1,a3-a2)。也就是这一段区间的差分的gcd。但是这个题目用不到,因为每次修改一个数,也就是单点修改,就直接维护gcd就行了。只要具有区间可合并性的各种题目,比如最值,总和,区间GCD。如果有单点修改,那就可以直接线段树。如果去区间修改的话,一般就要看看这个维护的东西的性质了。
2.然后就是是否接近x,最多让你修改一个数。这就和我以前做的那个线段树维护hash二分判断是否两个字符串接近的题目差不多科学幻想。对于可以删掉一个,那么就可以二分得到不一样的地方在哪,然后这里可以修改,剩下的左右两端区间必须要符合题意。
3.写完发现超时了。因为这样的复杂度是m*log(n)*log(n)。2e8吧,但是就超时了。所以就要换个判断方法。这时候想到以前做过的那道题:浙江省赛-Bin Packing Problem。这个题要找个最左端可以装下x的点,如果也二分去找,也超时了。然后就直接在树上找,对于l到r,看看左子树的最大值是否>=x,如果可以往左右,如果不可以往右走。直到搜到叶子节点,把x装下去就行了。如果最终没找到,那么就开一个新的盒子。
4.所以这个题也可以这样,直接在树上搜,这样复杂度是log(n)的,因为每次都可以扔掉一半,只去不合法的那一半。这个题,如果左区间的gcd不是x的倍数,就走左边,如果右边不是走右边。如果两边都不是那么就直接非法,因为最多改掉一个数,这样就出现两个不合法的了。当然对于这段区间在查询的区间范围内,但是都合法,就直接不搜了。两种不同的写法而已。一种是:就是刚才描述的,看看哪边不合法就走哪边,如果两边都不合法就是非法直接退出。一种是:就看看有多少非法的个数,累加起来,每次左右搜,但是这样两边都会搜,但是你写上如果这段区间都合法就return 0就可以,这样就不会搜下去了。
5.刚开始我以为最多一个点不等于x,然后其余的gcd==x。实际上,应该是看是否为x的倍数,因为6 6 12也是接近3的,因为我把其中一个改成3,剩下的区间gcd都是3的倍数也可以。当然求的时候,gcd可能是负数,取绝对值就可以了。但是正负不影响取模的是否=0的事。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define node_l node<<1
#define node_r node<<1|1

using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 5e5+20,M = 2010;

int T,n,m,k;
int va[N];
int maxn = 0;

int gcd(int a,int b)
{
	if(!b) return a;
	return gcd(b,a%b);
}

struct seg_gcd
{
	struct node{
		int L,R;
		int ans;
	}t[4*N];
	void pushup(int node)
	{
		t[node].ans = gcd(t[node_l].ans,t[node_r].ans);	
	}
	void build(int node,int l,int r)
	{
		t[node].L = l,t[node].R = r;
		if(l==r)
		{
			t[node].ans = va[l];
			return ;
		}
		int mid = (l+r)>>1;
		build(node_l,l,mid);build(node_r,mid+1,r);
		pushup(node);
	}
	void update(int node,int x,int value)
	{
		if(t[node].L==x&&t[node].R==x)
		{
			t[node].ans = value;
			return ;
		}
		int mid = (t[node].L+t[node].R)>>1;
		if(x<=mid) update(node_l,x,value);
		else update(node_r,x,value);
		pushup(node);
	}
	int query(int node,int l,int r)
	{
		if(t[node].L>=l&&t[node].R<=r) return t[node].ans;
		int mid = (t[node].L+t[node].R)>>1;
		if(r<=mid) return query(node_l,l,r);
		else if(l>mid) return query(node_r,l,r);
		else return gcd(query(node_l,l,mid),query(node_r,mid+1,r));
	}
	int check(int node,int l,int r,int c)
	{
		if(maxn>=2) return maxn; //如果此时非法了,就直接return 2
		if(t[node].L>=l&&t[node].R<=r&&t[node].ans%c==0) return 0; //如果这段区间没必要搜下去了,这里会省去很多时间,因为如果可以的话,每次只有一半区间是不合法的。
		if(t[node].L==t[node].R) return (t[node].ans%c!=0); //如果这个一个点是非法的就要操作一次
		int mid = (t[node].L+t[node].R)>>1;
		if(r<=mid) return maxn = check(node_l,l,r,c);
		else if(l>mid) return maxn = check(node_r,l,r,c);
		else return maxn = check(node_l,l,mid,c)+check(node_r,mid+1,r,c);
	}
}t_gcd;

signed main()
{
	IOS;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>va[i];
	t_gcd.build(1,1,n);
	cin>>m;
	while(m--)
	{
		int op,a,b,c;
		cin>>op;
		if(op==1)
		{
			cin>>a>>b>>c;
			maxn = 0;
			if(t_gcd.check(1,a,b,c)<=1) cout<<"YES\n";
			else cout<<"NO\n";
		}
		else
		{
			cin>>a>>b;
			t_gcd.update(1,a,b);
		}
	}
	return 0;
}
/*
二分超时的写法
bool check(int l,int r,int c)
{
	int anw = t_gcd.query(1,l,r);
	return anw%c==0;
}
if(op==1)
{
	cin>>a>>b>>c;
	int l = a,r = b;
	while(l<r)
	{
		int mid = (l+r+1)>>1;
		if(check(a,mid,c)) l = mid;
		else r = mid-1;
	}
	int mid = 0;
	if(check(a,l,c)) mid = l+2;
	else mid = l+1;
	if(mid>b||check(mid,b,c)) cout<<"YES\n";
	else cout<<"NO\n";
}
/*

interval GCD

题意:
就是给你一个n的数组,有两种操作,一种是让一段区间的数都加上D,一种是查询这一段区间的gcd。

思考:
1.这题很明显就和上一题不同,这里是区间修改,这样你就没法直接维护了。但是gcd有个性质,就是gcd(a1,a2,a3)=gcd(a1,a2-a1,a3-a2)也就是一些数的gcd就是他们差分的gcd。但是这个能干什么呢?
2.如果我们线段树维护这个数组的差分,那么区间修改对应差分来说就是单点修改,就是修改l和r+1这两个点。但是你维护差分,虽然把区间修改变成单点修改了,但是你怎么求gcd呢?
3.这里就用到了那个公式,维护差分的gcd。但是只能求出来1到n这一个区间的gcd呀,如果求l和r这段区间的gcd呢?直接query(l,r)求出来的不是l到r的gcd,因为你l点的值是va[l]-va[l-1],而应该是va[l]-0。所以每次你还要知道va[l]的值什么,然后gcd(va[l],query(l+1,r))这个就是真正的l到r的gcd。
4.所以由于我们维护的是差分,那么如果我们对差分再维护一个区间和,是不是如果查询query_sum(1,l)的值,这个值是不是就是真正的va[l],因为差分的前缀和就是原本的值。其实我们发现,对差分数组维护区间和,但是我们实际上只要求1到i的值,也就是前缀和。所以这个前缀和也可以用树状数组来维护。每次修改也是修改l和r+1就可以了。但是只写一个线段树更方便,所以这里就用线段树维护了。
5.到这里就可以维护区间修改的gcd了。但是每次查询区间gcd的复杂度是2*log(n)。因为每次你还要查询一个差分的前缀和。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define node_l node<<1
#define node_r node<<1|1

using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 5e5+20,M = 2010;

int T,n,m,k;
int va[N],vb[N];

int gcd(int a,int b)
{
	if(!b) return a;
	return gcd(b,a%b);
}

struct seg_gcd
{
	struct node{
		int L,R;
		int sum,ans;
	}t[4*N];
	void pushup(int node)
	{
		t[node].sum = t[node_l].sum+t[node_r].sum;
		t[node].ans = gcd(t[node_l].ans,t[node_r].ans);	
	}
	void build(int node,int l,int r)
	{
		t[node].L = l,t[node].R = r;
		if(l==r)
		{
			t[node].sum = vb[l];
			t[node].ans = vb[l];
			return ;
		}
		int mid = (l+r)>>1;
		build(node_l,l,mid);build(node_r,mid+1,r);
		pushup(node);
	}
	void update(int node,int x,int value)
	{
		if(t[node].L==x&&t[node].R==x)
		{
			t[node].sum += value;
			t[node].ans += value;
			return ;
		}
		int mid = (t[node].L+t[node].R)>>1;
		if(x<=mid) update(node_l,x,value);
		else update(node_r,x,value);
		pushup(node);
	}
	int query1(int node,int l,int r)
	{
		if(l>r) return 0;
		if(t[node].L>=l&&t[node].R<=r) return t[node].ans;
		int mid = (t[node].L+t[node].R)>>1;
		if(r<=mid) return query1(node_l,l,r);
		else if(l>mid) return query1(node_r,l,r);
		else return gcd(query1(node_l,l,mid),query1(node_r,mid+1,r));
	}
	int query2(int node,int l,int r)
	{
		if(t[node].L>=l&&t[node].R<=r) return t[node].sum;
		int mid = (t[node].L+t[node].R)>>1;
		if(r<=mid) return query2(node_l,l,r);
		else if(l>mid) return query2(node_r,l,r);
		else return query2(node_l,l,mid)+query2(node_r,mid+1,r);
	}
}t_gcd;

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>va[i];
	for(int i=1;i<=n;i++) vb[i] = va[i]-va[i-1];
	t_gcd.build(1,1,n);
	while(m--)
	{
		char op;
		int a,b,c;
		cin>>op;
		if(op=='C')
		{
			cin>>a>>b>>c;
			t_gcd.update(1,a,c);
			if(b+1<=n) t_gcd.update(1,b+1,-c);
		}
		else
		{
			cin>>a>>b;
			int anw = abs(gcd(t_gcd.query1(1,a+1,b),t_gcd.query2(1,1,a))); //gcd可能求出来是负数,取个绝对值就可以了。
			cout<<anw<<"\n";
		}
	}
	return 0;
}

小阳的贝壳

题意:
就是给你个n数组,有三种操作,一种是让l到r区间的数都加上x,一种是查询l到r所有相邻数差的绝对值的最大值,一种是询问l到r区间的最大公约数。

思考:
1.很明显,这题就是比上一题多了一个,查询所有相邻数差的绝对值的最大值。当然如果l==r,也就是只有一个数,那么就没有相邻了,答案就是0。
2.这怎么维护呢,其实对于差,很明显就是对差分数组维护区间最大值。然后修改的话,对于差分数组而言,修改一段区间就是修改两个端点,所以修改的时候也是单点修改,但是一定要先在vb这个原数组修改后,再让线段树里面的值 = abs(vb[l])。因为本身维护的是绝对值,并不是本身的值,如果你加上一个x,是不对的。比如本身是-2,+2后应该为0,但是线段树维护的是绝对值也就是本身是2,+2后变成4了就不对了。同时呢也要注意,比如查询l到r的差的绝对值最大值,l前面是没有数的,所以我们只看每个数减去前面的数的差就可以了,所以l这个点的值我们就不能看。也就是query(l+1,r)。还要注意的是,差分数组的第一个元素的值不能放进线段树里面,因为它前面是没有值的,算进去的话就会让这个线段树的最大值可能变大。反正要考虑好细节,到底怎么维护的。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define node_l node<<1
#define node_r node<<1|1

using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+20,M = 2010;

int T,n,m,k;
int va[N],vb[N];

int gcd(int a,int b)
{
	if(!b) return a;
	return gcd(b,a%b);
}

struct seg_gcd
{
	struct node{
		int L,R;
		int sum,ans,maxn;
	}t[4*N];
	void pushup(int node)
	{
		t[node].sum = t[node_l].sum+t[node_r].sum;
		t[node].ans = gcd(t[node_l].ans,t[node_r].ans);
		t[node].maxn = max(t[node_l].maxn,t[node_r].maxn);
	}
	void build(int node,int l,int r)
	{
		t[node].L = l,t[node].R = r;
		if(l==r)
		{
			t[node].sum = vb[l];
			t[node].ans = vb[l];
			if(l>1) t[node].maxn = abs(vb[l]); //1号点不更新
			return ;
		}
		int mid = (l+r)>>1;
		build(node_l,l,mid);build(node_r,mid+1,r);
		pushup(node);
	}
	void update(int node,int x,int value)
	{
		if(t[node].L==x&&t[node].R==x)
		{
			t[node].sum += value;
			t[node].ans += value;
			if(x>1) t[node].maxn = abs(vb[x]); //1号点永远都不更新
			return ;
		}
		int mid = (t[node].L+t[node].R)>>1;
		if(x<=mid) update(node_l,x,value);
		else update(node_r,x,value);
		pushup(node);
	}
	int query1(int node,int l,int r)
	{
		if(l>r) return 0;
		if(t[node].L>=l&&t[node].R<=r) return t[node].ans;
		int mid = (t[node].L+t[node].R)>>1;
		if(r<=mid) return query1(node_l,l,r);
		else if(l>mid) return query1(node_r,l,r);
		else return gcd(query1(node_l,l,mid),query1(node_r,mid+1,r));
	}
	int query2(int node,int l,int r)
	{
		if(t[node].L>=l&&t[node].R<=r) return t[node].sum;
		int mid = (t[node].L+t[node].R)>>1;
		if(r<=mid) return query2(node_l,l,r);
		else if(l>mid) return query2(node_r,l,r);
		else return query2(node_l,l,mid)+query2(node_r,mid+1,r);
	}
	int query3(int node,int l,int r)
	{
		if(t[node].L>=l&&t[node].R<=r) return t[node].maxn;
		int mid = (t[node].L+t[node].R)>>1;
		if(r<=mid) return query3(node_l,l,r);
		else if(l>mid) return query3(node_r,l,r);
		else return max(query3(node_l,l,mid),query3(node_r,mid+1,r));
	}
}t_gcd;

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>va[i];
	for(int i=1;i<=n;i++) vb[i] = va[i]-va[i-1];
	t_gcd.build(1,1,n);
	while(m--)
	{
		int op,a,b,c;
		cin>>op>>a>>b;
		if(op==1)
		{
			cin>>c;
			vb[a] += c;
			t_gcd.update(1,a,c);
			if(b+1<=n)
			{
				vb[b+1] -= c;
				t_gcd.update(1,b+1,-c);
			}
		}
		if(op==2)
		{
			if(a==b) cout<<0<<"\n";
			else cout<<t_gcd.query3(1,a+1,b)<<"\n";
		}
		if(op==3)
		{
			int anw = abs(gcd(t_gcd.query1(1,a+1,b),t_gcd.query2(1,1,a)));
			cout<<anw<<"\n";
		}
	}
	return 0;
}

总结:
多多思考,注意细节,多多注意各种细致的点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值