2021CCPC网络赛重赛1005 Monopoly题解

本文详细解析了2021年CCPC网络赛重赛中第1005题Monopoly的题目内容和解题思路。针对每组数据,给出了前缀和数组处理方法,通过优化前缀和的存储和查询,降低了暴力枚举的时间复杂度。文章介绍了如何通过对数组进行排序和二分查找来快速找到满足条件的前缀和,以及处理特殊情况的细节,如数组和为负数的情况。此外,提供了完整的C++代码实现,展示了算法的具体应用。
摘要由CSDN通过智能技术生成

2021CCPC网络赛重赛1005 Monopoly题解

题意

多组数据,每组数据一个长度为n的数组a,m个询问。记数组a的前缀和为sum,对每个询问x,找到最小的长度len,使得x=sum[len%n]+sum[n]*(len/n)


分析

  • 考虑暴力,每次询问暴力枚举前缀sum[i],i from 0 to n-1,再检查x-sum[i] 能否被sum[n]整除即可,复杂度O(n2)。
  • 对于公式 x=sum[len%n]+sum[n]*(len/n) 等号两边同时对sm[n]取模,得到x%sum[n]=sum[len%n]%sum[n]。即对于每个询问x,合法的前缀要满足这个式子。
  • 因为sum[i]有n个,sum[len%n]%sum[n]最多也只有n个,所以可以提前存储sum[len%n],对于每次询问x只找合法的sum[len%n]来加速枚举。
  • 上面的加速显然可以被特定数据卡回暴力复杂度。但我们要最小化len,而在 len=len%n+(len/n)n 中,显然len%n小于 (len/n)n,所以我们要最小化len/n,也就是要最小化 x=sum[len%n]+sum[n]*(len/n) 中sum[n]的使用次数所以当sum[n]>0时sum[len%n]要为最接近x的数值。
  • 每次询问时,找到最大的小于x的sum[len%n],且满足x%sum[n]=sum[len%n]%sum[n]。所以这里就可以给sum[len%n]排序了,以**sum[len%n]%sum[n]**为第一关键字,**sum[len%n]**为第二关键字排序,每次就可以通过二分来查找了。该次询问的答案即为
    len=len%n+(len/n)n

小细节

  • sm[n]=0要特判
  • 考场上傻傻的直接对sm[n]取模导致本质相同的sum[i]%sm[n]有的正有的负,故x%sm[n]忽略了一部分可取的sm[i]。所以要统一sum[i]%sm[n]为正,但这样又要统一sum[n]为正。故sum[n]<0时,需要将所有的sum[i]和x取反来保证sum[n]>0且答案正确。
  • 对于sum和sum%sum[n]均相同的sum,需要只保留len%n最小的那个,其余舍弃,便于二分。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int mx=100005;
int t,n,m,cnt;
struct node{
	ll sm,id;
	int ip;
}po[mx];
inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
	return x*f;
}
inline bool cmp(node a,node b){
	if(a.id==b.id){
		if(a.sm==b.sm) return a.ip<b.ip;
		return a.sm<b.sm;
	}
	return a.id<b.id;
}
int main()
{
//	freopen("1005.in","r",stdin);
//	freopen("a.out","w",stdout);
	t=read();
	for(int op=1;op<=t;++op){
		n=read();m=read();
		ll sm=0,p,rp;
		bool flag=false;
		po[0].sm=0;po[0].ip=0;
		for(int i=1;i<n;++i){
			sm+=read();po[i].sm=sm;
			po[i].ip=i;
		}
		sm+=read();
		if(sm<0){
			flag=true;sm=-sm;
			for(int i=0;i<n;++i) po[i].sm=-po[i].sm;
		}
		for(int i=0;i<n;++i) po[i].id=sm!=0?(po[i].sm%sm+sm)%sm:0;
		sort(po,po+n,cmp);cnt=0;
		for(int i=1;i<n;++i) if(po[i].id!=po[i].id||po[i].sm!=po[i-1].sm) po[++cnt]=po[i];
		if(sm==0){
			for(int i=1;i<=m;++i){
				p=read();
				int l=0,r=cnt,mid;
				while(l<r){
					mid=(l+r)>>1;
					if(po[mid].sm<p) l=mid+1;
					else r=mid;
				}
				if(po[l].sm!=p) printf("-1\n");
				else printf("%d\n",po[l].ip);
			}
		}
		else
		for(int i=1;i<=m;++i){
			p=read();if(flag) p=-p;
			rp=(p%sm+sm)%sm;
			int l=0,r=cnt,mid;
			while(l<r){
				mid=(l+r+1)>>1;
				if(po[mid].id<rp) l=mid;
				else if(po[mid].id>rp) r=mid-1;
				else if(po[mid].sm>p) r=mid-1;
				else l=mid;
			}
			if(po[l].id!=rp) printf("-1\n");
			else if(sm>0&&po[l].sm>p) printf("-1\n");
			else if(sm<0&&po[l].sm<p) printf("-1\n");
			else printf("%lld\n",po[l].ip+n*(p-po[l].sm)/sm);
		}
		
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hiroxzwang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值