Harbour.Space Scholarship Contest 2021-2022 (open for everyone, rated, Div. 1 + Div. 2) (E.思维题 F.数论)

本文探讨了两种算法题目,一是关于序列的右移与交换次数限制问题,核心在于分析交换过程中元素归位的情况;二是数论中的配对模运算,通过维护区间和来动态计算数组元素对模运算后的贡献。这两个问题都涉及到高效的数组操作和数学洞察力,适合算法爱好者深入研究。

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

E. Permutation Shift(思维题)

长为n的排列,初始为1 2 3  ... n,

你可以初始右移k步,比如右移一步的序列就是2 3 ... n 1,

你有m(0<=m<=n/3)次交换机会,每次可以交换两个任意位置,

给定一个目标排列,

问存在多少种右移偏移量k,

使得序列能通过不超过m次交换,

换成给定的目标排列,输出所有的k

诈骗题,一个显然的事实是,经过一次交换最后有2个位置归位,

所以m次只能最多2m个归位,需要初始情况需要满足至少已经有n/3个归位了,

这样的偏移量不会超过三个,所以找到所有合法的偏移量,暴力判断能不能m步换回来即可

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int t,n,m,a[N],dis[N],par[N];
vector<int>res;
int find(int x){
	return par[x]==x?x:par[x]=find(par[x]);
}
int main(){
	scanf("%d",&t);
	while(t--){
		res.clear();
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;++i){
			dis[i]=0;
		}
		for(int i=0;i<n;++i){
			scanf("%d",&a[i]);
			a[i]--;
			dis[(i-a[i]+n)%n]++;//i需要左挪dis个值归位 
		}
		for(int i=0;i<n;++i){
			if(dis[i]>=n/3){
				for(int j=0;j<n;++j){
					par[j]=j;
				}
				int cnt=0;
				for(int j=0;j<n;++j){
					int u=find(a[j]),v=find((j-i+n)%n);//如果j的值左挪i个恰好与aj相同 说明无需改变 
					if(u!=v)par[v]=u,cnt++;
				}
				if(cnt<=m){
					res.push_back(i);
				}
			}
		}
		printf("%d",(int)res.size());
		for(auto &v:res)printf(" %d",v);
		puts("");
	}
	return 0;
} 

F. Pairwise Modulo(数论)

有一个n(n<=2e5)个不同整数的数组,第i个数为ai(1<=ai<=3e5)

定义p函数为,p_{k} = \sum_{1 \leq i,j \leq k}a_{i}\ mod\ a_{j}

对每个i从1到n,输出pi的值

做法很显然,即考虑加入最后一个数时候的新的贡献,

设当前加入的是数值是7,则贡献分为两部分,aj%7和7%aj,

aj%7,即aj-[aj/7]*7,考虑枚举区间范围,

[aj/7]的值从0,1..往上枚举,统计区间内的值的和,

对于aj的部分求和,显然是已经插入的值的总和,

7%aj,即7-[7/aj]*aj,即考虑7内能数出几个aj,

把aj分别插入aj,2aj,3aj...的位置,即可统计区间内的和

对于7的部分求和,显然是(i-1)个7,

所以需要开两棵BIT,分别维护上述两个区间和

#include<bits/stdc++.h>
using namespace std;
const int N=3e5,M=N+10;
typedef long long ll;
int n;
ll a[M],ans,now;
struct BIT{
	ll n,tr[M];
	void init(int _n){
		n=_n;
		memset(tr,0,sizeof tr);
	}
	void add(int x,ll v){
		for(int i=x;i<=n;i+=i&-i)
		tr[i]+=v;
	}
	ll sum(int x){
		if(x<0)return 0;
		ll ans=0; 
		for(int i=x;i;i-=i&-i)
		ans+=tr[i];
		return ans;
	}
}d[2];
//设当前插入的数是7 
//ai%7:ai-[ai/7]*7 枚举[0,7) [7,14) 
//7%ai:7-[7/aj]*aj 实际是统计<=7有几个aj 对aj的倍数+=aj 
int main(){
	scanf("%d",&n);
	for(int i=0;i<2;++i){
		d[i].init(N);
	}
	for(int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
		ans+=now;
		for(int j=0;j<=N/a[i];++j){
			int l=a[i]*j,r=min((ll)N+1,l+a[i]);//[l,r)
			ans-=1ll*(d[0].sum(r-1)-d[0].sum(l-1))*j*a[i];
		}
		d[0].add(a[i],1);
		ans+=1ll*(i-1)*a[i];
		ans-=d[1].sum(a[i]);
		for(int j=a[i];j<=N;j+=a[i]){
			d[1].add(j,a[i]);
		}
		now+=a[i];
		printf("%lld%c",ans," \n"[i==n]);
	} 
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值