1971 F. Range Update Point Query-树状数组或并查集

F. Range Update Point Query

题目传送门
题意:
给你 n 个数依次排列,你需要进行 q 次操作。每次操作要么问你下标为x的值,要么让你对区间 [l,r] 执行操作:\(a_i=a_i的数位和,如420=(6),87=(15)\)
思路:

法一:树状数组

利用差分树状数组来维护所有的\(a_i\)需要维护的次数,前缀和即为需要修改的次数

//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
//#define int long long
// typedef std::pair<int,int> pii;
// typedef std::pair<ll,ll> pll;
// typedef std::pair<ull,ull> pull;

inline ll read()
{
	ll 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-48;ch=getchar();}
	return x*f;
}

using namespace std;

const int maxm=2e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,q,a[maxm],qz[maxm];

int lowbit(int x){//经典lowbit
	return x&(-x);
}

void update(int x,int v){//单点修改
	while(x<=n){
		qz[x]+=v;
		x=x+lowbit(x);
	}
	return ;
}

int getsum(int x){//求和
	int ans=0;
	while(x>0){
		ans+=qz[x];
		x-=lowbit(x);
	}
	return ans;
}

int calc(int x){//题目要求的数字update操作
	int ans=0;
	while(x){
		ans+=x%10;
		x/=10;
	}
	return ans;
}

void solve(){
	cin>>n>>q;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		qz[i]=0;
	}
	int c,x,y;
	while(q--){
		cin>>c;
		if(c==1){
			cin>>x>>y;
			update(x,1);
			update(y+1,-1);
		}else{
			cin>>x;
			int ans=a[x],t;
			if(ans>9){
				t=getsum(x);
				while(t--){
					ans=calc(ans);
					if(ans<10) break;
				}
			}
			cout<<ans<<'\n';
		}
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}
/*
wa原因:在利用树状数组维护差分和之后,在每次查询完之后
因为自己没有将差分数组清零
所以在下次update时,就会出现update多几次的情况出现
或者说,都用了树状数组了,就不需要清零了,因为前缀和就是update的次数
*/

法二:dsu

就是利用并查集的思想,数据范围较大,暴力解会超时。但是我们发现一个数换成它的数位和的话,同一个数即使很大,操作次数也是很小的,并且在变成只有个位数之后不需要我们再进行操作了。如 999999999->81->9。所以我们在每次进行更新操作的时候,我们立即对a数组进行更新,如果说\(a_i\)在这时已经是个位数了,那么我们将\(a_i\)的父亲指向\(a_{i+1}\),那么下次遍历的时候,给定的l,我们都从x=ds.findfa(l)开始,当\(x<r\)的时候,继续遍历,\(x=findfa(x+1)\),直至结束。
很溜的并查集思想,用来去掉无用数据!!!

//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
//#define int long long
// typedef std::pair<int,int> pii;
// typedef std::pair<ll,ll> pll;
// typedef std::pair<ull,ull> pull;

inline ll read()
{
	ll 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-48;ch=getchar();}
	return x*f;
}

using namespace std;

const int maxm=2e5+5,inf=0x3f3f3f3f,mod=998244353;

struct dsu{
	int num;
	vector<int> fa;
	dsu(int x):num(x),fa(x+1){
		for(int i=0;i<=x;++i){
			fa[i]=i;
		}
	}
	int findfa(int x){
		return fa[x]==x?x:fa[x]=findfa(fa[x]);
	}
	void merge(int x,int y){
		fa[findfa(x)]=findfa(y);
		return ;
	}
};

int calc(int x){
	int ans=0;
	while(x){
		ans+=x%10;
		x/=10;
	}
	return ans;
}

void solve(){
	int n,q;
	cin>>n>>q;
	vector<int> a(n+5);
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	dsu ds(n+5);//记得开大一点,后面需要用到
	int c,l,r,x;
	while(q--){
		cin>>c;
		if(c==1){
			cin>>l>>r;
			int x=ds.findfa(l);
			while(x<=r){
				a[x]=calc(a[x]);
				if(a[x]<10){
					ds.merge(x,x+1);//这里的写法与merge的写法有关
				}
				x=ds.findfa(x+1);
			}
		}else{
			cin>>x;
			cout<<a[x]<<'\n';
		}
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

进阶:两者结合使用

详见920 F. SUM and REPLACE

posted on 2023-06-27 16:29  Qiansui  阅读(18)  评论(0编辑  收藏  举报