[2019年ACM-ICPC 徐州网络赛] I.query (机智的维护方法+离线树状数组

题目链接:I. query

题意:
给两个数n,m,分别表示序列中有n个数,和m组询问。
接下来n个数pi,表示1-n的一个排列。
接下来m个询问,输出[l,r]中满足min(pi.pj)=gcd(pi,pj)的对数,即[l,r]区间内有多少对“倍数对”。
数据范围:m≤105,n≤105

解法:
考虑一个树状数组,原数据a[i]表示从i开始到当前的r,能与i形成的倍数对的个数。那么维护这个数字,再根据询问区间的r排序,更新这个a[i],那对每组询问,答案就是query[l,r]。如何维护这个a[i],是很机智的。(自认为)

首先读入排列p的时候把每个数pi对应的下标存起来。
接着用vector G将每个pi的倍数(且不大于n)的下标推到G[pi]的后面,同时把pi的下标i推到这个倍数后面。
那么G[i]对每个i,都存储了它的因数和倍数的下标,即记录了能和它成对的数字。
将每个G[i]后面的下标从小到大排个序。方便对每个询问依次插入。

AC代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int mod = 1e9+7;
const double eps = 1e-5;
const int N = 1e5+10;

void redirect() {
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
        //freopen("out.txt","w",stdout);
    #endif
}
inline ll read() {
    ll f=1,x=0;char ch;
    do {ch=getchar(); if(ch=='-') f=-1;} while (ch<'0'||ch>'9');
    do {x=x*10+ch-'0'; ch=getchar(); } while (ch>='0'&&ch<='9');
    return x*f;
}

struct Node{
	int l,r;int idx;ll ans;
} Q[N];

bool cmp_1 (Node a, Node b) {
	return a.r<b.r;
}

bool cmp_2 (Node a, Node b) {
	return a.idx<b.idx;
}

vector<int> G[N];
int n,m;int vis[N];int a[N],pos[N],nxt[N];ll c[N];

int lowbit (int x) {
    return x&(-x);
}

void update (int i,ll k) {
    while(i <= n){
        c[i] += k;
        i += lowbit(i);
    }
}

ll sufsum (int i) {//A[1-i]的和
    ll res = 0;
    while (i) {
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

ll query(int l,int r){
	return sufsum(r)-sufsum(l-1);
}

int main() {
	redirect();
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) {
		scanf("%lld",&a[i]); pos[a[i]]=i;
	}
	for (int i=1;i<=n;i++) {
		int t = a[i]*2;
		while ( t <= n ) {
			G[t].push_back(i);
			G[a[i]].push_back(pos[t]);
			t+=a[i];
		}
	}
	
	for(int i=1;i<=n;i++) sort(G[i].begin(),G[i].end());
	
	for ( int i=1;i<=m;i++ ) {
		scanf("%d%d", &Q[i].l, &Q[i].r); Q[i].idx = i;
	}
	sort ( Q+1, Q+m+1, cmp_1 );
	
	int pt=1;
	for(int i=1;i<=m;i++){
		while( pt<=Q[i].r && pt<=n ) {
			int k=0; int x=a[pt];
			for ( k = nxt[x]; k < G[x].size(); k++ ) {
				if ( G[x][k] < pt ) update(G[x][k],1);
				else break;
			}
			nxt[x] = k;
			pt++;
		}
		Q[i].ans = query(Q[i].l,Q[i].r);
	}
	
	sort( Q+1, Q+m+1, cmp_2 );
	
	for(int i=1;i<=m;i++) printf("%lld\n",Q[i].ans);
}

/*
---------------------
  author:dragon_bra
---------------------
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值