2018.10.24【校内模拟】小 C 的序列(数论)(链表)

传送门


解析:

本来可以拿 80 p t s 80pts 80pts暴力的
然后 u n o r d e r e d   m a p − > m a p unordered\text{ }map->map unordered map>map − 20 p t s -20pts 20pts
S T ST ST − > -> >线段树, − 20 p t s -20pts 20pts

结果 40 p t s 40pts 40pts全场最低滚粗。

这道题正解比暴力不知道高到那里去了,而且又短又好写。

思路:

暴力做法:

先讲一下怎么暴力做吧,不枉我考场上想得这么辛苦(大佬直接跳到后面看吧)。。。

显然我们如果固定一个起点,考虑终点分布对区间 g c d gcd gcd的影响。显然不同的 g c d gcd gcd最多只有 l o g A logA logA种,因为每次 g c d gcd gcd的变化必然是从原来的 g c d gcd gcd中减少至少一个质因子,最坏情况就是每次只减少了一个 2 2 2,所以最多只有 l o g A logA logA种不同的 g c d gcd gcd

显然 g c d gcd gcd区间的变化是单调的,那么我们考虑二分求出这 l o g   n log\text{ }n log n个区间,这里复杂度就是 l o g 2 n log^2n log2n,然后我们还需要查询区间 g c d gcd gcd g c d gcd gcd的复杂度就是一个 O ( l o g   n ) O(log\text{ }n) O(log n),然后考虑数据结构维护区间询问,用 S T ST ST表维护可以做到 O ( 1 ) O(1) O(1)提取区间。

而蒟蒻的我考场上写了线段树。。。而且暴力分还需要 u n o r d e r e d   m a p unordered\text{ }map unordered map卡常。。。告辞

正解:

考虑我们必然会对每一个点求出所有 g c d gcd gcd对应的区间,那么这些区间的分界点就不能够再利用了吗?

显然是可以的,这就是这道题的关键,怎么利用原来 g c d gcd gcd的分界点?

考虑我们每次合并必然是两个相邻的区间,链表大法好。

我们考虑倒着枚举区间的左端点(当然也可以顺着枚举右端点),每次在原来的分界点中条链表,如果发现两个区间在加上新枚举的左端点后 g c d gcd gcd相等了,就直接合并这两个区间。

这样做显然是对的,因为我们是倒着枚举左端点,那么区间的 g c d gcd gcd在某个时刻一旦相等,那么再加上前面新增的节点也肯定相等。

我知道肯定还有同学一头雾水(今天写题解前至少三批同学问我这个问题),其实就是因为 g c d ( a , g ) = = g c d ( a , g ) gcd(a,g)==gcd(a,g) gcd(a,g)==gcd(a,g)当两个区间 &lt; l , r &gt; &lt;l,r&gt; <l,r> &lt; l , r + 1 &gt; &lt;l,r+1&gt; <l,r+1>的区间 g c d gcd gcd都为 g g g的时候,新增的节点 l − 1 l-1 l1产生的影响就是上面这个式子,显然它们永远都在一个区间里面。

显然统计答案需要用到平衡树 m a p map map或者哈希表 u n o r d e r e d   m a p unordered\text{ }map unordered map,这里明显是查找操作较多,用 H a s h Hash Hash表要优一些(我考场上怎么没想到呢。。。)


代码:

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

inline void outint(ll a){
	static char ch[23];
	if(a==0)pc('0');
	while(a)ch[++ch[0]]=a-a/10*10,a/=10;
	while(ch[0])pc(ch[ch[0]--]^48);
}

inline int gcd(int a,int b){
	int tmp;
	while(b){
		tmp=a-a/b*b;
		a=b;
		b=tmp;
	}
	return a;
}

cs int N=500005;
int n,m;
int nxt[N],a[N],b[N];
tr1::unordered_map<int,ll>cnt;

signed main(){
	n=getint();
	m=getint();
	for(int re i=1;i<=n;++i)a[i]=getint(),nxt[i]=i+1;
	for(int re i=1;i<=m;++i)b[i]=getint(),cnt[b[i]]=0;
	for(int re i=n;i;--i){
		int pre=i;
		for(int re j=i;j<=n;j=nxt[j]){
			a[j]=gcd(a[j],a[pre]);
			if(a[j]==a[pre])nxt[pre]=nxt[j];
			tr1::unordered_map<int,ll>::iterator it=cnt.find(a[j]);
			if(it!=cnt.end())it->second+=1ll*nxt[j]-j;
			pre=j;
		}
	}
	for(int re i=1;i<=m;++i)outint(cnt[b[i]]),pc(' ');
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值