上着英语网课写这个总结,真是有点冲。
就简单分享两个题目,总结一下:
Problem A
给出一段序列a(a_1,a_2......a_n),Q次询问L,R,Z求:
简单分析一下这个题目:
gcd合并之后只会变小或者不变,所以题目就换成了,在区间L,R内,哪个数与z的gcd最大。
第一次的思路是二分,类似口算训练的二分:
因为gcd(X,Y)可以看为X与Y的最大的公共因子,所以可以筛法求一下因子。
所以,每次询问将Z分解,题目就变成了 区间[L,R]内有没有Z的因子。
所以,以vector存一下因子出现的位置,进行二分,判断该因子在这个区间内里是否出现过。
复杂度 O(m*sqrt(z)*lgn)
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include<stdio.h>
#include<vector>
#include<algorithm>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
const double PI=acos(-1);
typedef long long ll;
typedef pair<ll,ll> pp;
const ll INF=1e18;
const ll maxn=1e5+18;
const int mod=1e9+7;
const double eps=1e-6;
inline bool read(ll &num)
{char in;bool IsN=false;in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
ll num[maxn];
vector<int>v[maxn];
vector<int>g[maxn];
int main()
{
read(n);read(m);
ll sz=-INF;
for(int k=100005;k>=1;k--){
for(int j=k;j<=100005;j+=k)
g[j].push_back(k);
}
for(int i=1;i<=n;i++){
read(num[i]);
ll temp=num[i];
for(int k=1;k*k<=temp;k++){
if(temp%k==0){
v[k].push_back(i);
v[temp/k].push_back(i);
}
}
}
while(m--){
ll x,y,z;
read(x);read(y);read(z);
ll res=0;
for(ll temp:g[z]){
if(v[temp].size()){
int f=upper_bound(v[temp].begin(),v[temp].end(),y)-lower_bound(v[temp].begin(),v[temp].end(),x);
if(f){
printf("%lld\n",temp);
break;
}
}
}
}
return 0;
}
第二种思路:莫队思想优化
考虑对上面的思路进行优化:
判断一个数在这个区间内是否出现过——因为询问的是区间,所以只需要知道最后一次出现的位置即可。
最简单的判断x的是否在区间[L,R]是否出现过,只需要按右端点排序,然后利用莫队思想每次区间增加一个数,就将这个数最后出现的位置更新。最后只需要判断左端点与当前这个数最后一次出现的位置的关系即可。
复杂度O(m*C)C为常数
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include<stdio.h>
#include<vector>
#include<algorithm>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
const double PI=acos(-1);
typedef long long ll;
typedef pair<ll,ll> pp;
const ll INF=1e18;
const ll maxn=1e6+18;
const int mod=1e9+7;
const double eps=1e-6;
inline bool read(ll &num)
{char in;bool IsN=false;in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
struct node{
int l,r,x;
int id;
bool friend operator<(node a,node b){
return a.r<b.r;
}
}q[maxn];
vector<int>g[maxn];
int pos[maxn];
ll num[maxn];
int ans[maxn];
void add(int x,int y){
for(int temp:g[x])
pos[temp]=y;
}
int main()
{
for(int k=100005;k>=1;k--){
for(int j=k;j<=100005;j+=k)
g[j].push_back(k);
}
read(n);read(m);
for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].x);
q[i].id=i;
}
sort(q+1,q+1+m);
int p=1;
for(int i=1;i<=m;i++){
while(p<=q[i].r) add(num[p],p),p++;
for(int temp:g[q[i].x]){
// printf("%d\n",temp);
if(pos[temp]>=q[i].l){
ans[q[i].id]=temp;
break;
}
}
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
第三种思路:用主席树直接维护有没有出现过,复杂度与第一种基本相同这里不再赘述
Problem B
给出一段序列a,m次询问L,R,Z,每次回答区间L,R内出现次数与Z互质的数的个数
简单分析一下:
首先静态区间询问,无修改——可以莫队写
1.用两个辅助数组vis[i]代表数字i出现的次数,cot[i]表示出现次数为i的数的个数
考虑区间增加和区间缩小的对答案的影响:
区间增加——a_i的出现次数增加,并且因为此时a_i出现次数改变了,所以cot[vicos[a_i]]++,但需要消除未加之前的影响所以还需要把之前的vis[a_i]的数量减掉
区间减少——a_i的出现次数减少,cot[vicos[a_i]]--;其余操作取反就可以。
2.考虑到一个性质:n个数字中出现的每个数出现次数不同的最多有sqrt(n)个
证明:
所以我们可以每次遍历这个不同的次数,判断该次数X是否与Z互质,如果与Z互质,那么答案就加上出现次数为X的数的个数即cot[x]
3.上面过程可以考虑用unorder_map去询问,但是复杂度还是会T,正确的解法是静态链表 O1修改。
所以总体的复杂度:m*sqrt(n)
AC:
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include<stdio.h>
#include<algorithm>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef pair<int,int> pp;
const ll INF=1e17;
const int maxn=1e5+18;
const int mod=1e9+7;
inline bool read(ll &num)
{char in;bool IsN=false;in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
int bel[maxn];
struct node{
int l,r,id;
int qr;
bool friend operator<(node a,node b)
{
if(bel[a.l]!=bel[b.l]) return a.l<b.l;
return a.r<b.r;
}
}q[maxn];
ll num[maxn];
int vis[maxn];
int cot[maxn];
int res[maxn];
int pre[maxn],cur[maxn];
int s=-1;///头指针
void ins(int x)///插入新节点
{
if(!x) return ;
if(!cot[x]){
cur[x]=s;
if(s!=-1) pre[s]=x;
s=x;pre[s]=-1;
}
cot[x]++;
}
void Del(int x)
{
if(!x) return;
cot[x]--;
if(cot[x]) return;
if(pre[x]!=-1) cur[pre[x]]=cur[x];
else s=cur[x];
if(cur[x]!=-1) pre[cur[x]]=pre[x];
}
void gg(int x,int w)
{
Del(vis[x]);
vis[x]+=w;
ins(vis[x]);
}
int gcd(int a,int b)
{
if(!a)return b;
return gcd(b%a,a);
}
int main()
{
read(n);read(m);
int block=sqrt(n);
for(int i=1;i<=n;i++) read(num[i]);
for(int i=1;i<=n;i++) bel[i]=(i-1)/block+1;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].qr);
q[i].id=i;
}
int L=1,R=0;
sort(q+1,q+1+m);
for(int i=1;i<=m;i++){
while(R<q[i].r) gg(num[++R],1);
while(L>q[i].l) gg(num[--L],1);
while(L<q[i].l) gg(num[L++],-1);
while(R>q[i].r) gg(num[R--],-1);
int sum=0;
for(int j=s;j!=-1;j=cur[j]){
if(gcd(j,q[i].qr)==1) sum+=cot[j];
}
res[q[i].id]=sum;
}
for(int i=1;i<=m;i++) printf("%d\n",res[i]);
return 0;
}
最后对一下这篇文章的标题对互质和莫队进行一下思路的总结:
1.关于互质的询问,可以将问题转换为是否有共同的非1因子。
2.莫队是一个思想即区间增加和区间修改对最后的答案有没有影响,不一定需要按照模板走,例如上面A题。
3.根据A题可以衍生出很多问题的变式:
(1)区间内X是否出现过
(2)将A题增加单点修改
(3)判断一个数的倍数在这个区间内是否出现过
(4)判断区间内....出现过
总而言之,出现过就说明与数量无关,又询问的是区间所以完全不需要考虑数量,按右端点排序考虑左端点和增加元素的影响就可以了。
4.B题最重要的是静态链表和莫队结合,B题在于推出结论才敢写:出现次数最多有sqrt(n)个——否则不敢轻易评估复杂度。
静态链表支持O1修改时间复杂非常优秀,但是如果数据范围到达1e9,还能开嘛?当然可以只需要进行一下离散化处理就可以了。
好了英语课下课了,我也总结完了。