HDU6102(树状数组 + 容斥 + 离线处理)

GCDispower

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 141    Accepted Submission(s): 70


Problem Description
There is a mysterious organization called GCD. They get a permutation P, permutation is a sequence of length N, only consists of number 1 to N and each number appears only once.
They want to gain magic power from this permutation. For the interval [L, R] they can get power:
i=LRj=i+1Rk=j+1R(gcd(P[i],P[j])==P[k])P[k]
Now they have M queries, for each query can you help them calculate how many power they can get?
 

Input
The first line is an integer T which indicates the case number.
And as for each case, first line is two integer N and M which indicates the length of permutation and number of query.
Next line is N integer which indicate the permutation P
Next M line, for each line is two integer L and R which indicate each query.

Limit
1T100
1N,M100000
1PiN , for any pair  (i,j) PiPj
1LiRiN

About 95% test case:  1N1000
Other test case:  1N100000
 

Output
As for each case, you need to output M integer which indicate the answer of each query.
 

Sample Input
  
  
2 3 1 3 2 1 1 3 6 3 6 5 4 3 2 1 1 6 2 6 3 5
 

Sample Output
  
  
1 8 5 0
 

Source
题意:给你一个序列,然后给你若干个询问,每个询问一个区间[l,r],问你这个区间的价值是多少,一个区间的价值我们定义为,若这个区间存在一个三元组i, j, k,使得gc(a[i], a[[j]) == a[k],则这个区间的价值加上a[k],(刚开始为0)。

解题思路:我们肯定是要把这些询问离线处理,然后一个最重要的一点是要枚举a[k],从左到右,我们可以知道如果gcd(a[i], a[j])  == a[k],则a[i] / a[k], 与a[j] / a[k] ,一定互质,所以我们肯定要枚举a[k]的倍数,并且位置在k的前面,处理出所有的倍数之后,我们从右往左扫,假如我们每次知道当前有多少对互质的数假设为x,我们就知道对区间左端点的贡献为x * a[k],我们可以用一个树状数组,或者线段树来维护一下就行, 关键是怎样知道当前有多少对互质的数,我们可以用容斥求出,我们可以每次记录一个数进行素数分解之后的所有可能的质因子乘积,然后后面的数通过容斥原理,先进行素数分解,然后枚举gcd为他的每一个质因子的倍数,这些数一定与当前数不互质,用总的数的个数减去这些数就行,我们可以用容斥求这些集合的并。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 10;
int N, M;
vector<int> prime[maxn];//素数分解,之后的质因子
bool valid[maxn];//素数筛选
int a[maxn];//原数组
int Hash[maxn];//元素i在原数组的位置
vector<pair<int, int>> query[maxn];//query[i]表示以i为右端点的区间,query[i][j].first表示询问id,query[i][j].second表示询问区间的左端点
ll ans[maxn];//用于容斥的数组,ans[i]表示i在之前出现过的次数
vector<pair<int, int>> status[maxn];//stastus[i][j].first表示状态的大小,second表示容斥的符号
ll Tree[maxn];//树状数组
ll result[maxn];//保存结果
int temp[maxn];//辅助数组
int lowbit(int x)
{
    return x&(-x);
}
void add(int loc, ll value)
{
    for(int i = loc; i <= N; i += lowbit(i))
    {
        Tree[i] += value;
    }
}
void update(int l, int r, ll value)
{
    add(l, value);
    add(r + 1, -value);
}
ll get(int loc)
{
    ll res = 0;
    for(int i = loc; i >= 1; i -= lowbit(i))
    {
        res += Tree[i];
    }
    return res;
}
void updateAns(int x, ll value)
{
    for(int i = 0; i < status[x].size(); i++)
    {
        ans[status[x][i].first] += value;
    }
}
ll getAns(int x)
{
    ll res = 0;
    for(int i = 0; i < status[x].size(); i++)
    {
        res += ans[status[x][i].first] * status[x][i].second;
    }
    return res;
}
void initPrime()
{
    memset(valid, true, sizeof(valid));
    for(int i = 0; i < maxn; i++)
    {
        prime[i].clear();
        status[i].clear();
    }
    for(int i = 2; i < maxn; i++)
    {
        if(valid[i])
        {
            for(int j = i; j < maxn; j += i)
            {
                valid[j] = false;
                prime[j].push_back(i);
            }
        }
    }
    for(int i = 1; i < maxn; i++)
    {
        for(int j = 0; j < (1<<(prime[i].size())); j++)
        {
           status[i].push_back(make_pair(1, 1));
           for(int k = 0; k < prime[i].size(); k++)
           {
               int num = (j>>k)&1;
               if(num)
               {
                   status[i][j].first *= prime[i][k];
                   status[i][j].second *= -1;
               }
           }
        }
    }
}
void init()
{
    memset(Tree, 0, sizeof(Tree));
    memset(ans, 0, sizeof(ans));
    for(int i = 1; i <= N; i++)
    {
        query[i].clear();
    }
}
int main()
{
    initPrime();
    //cout<<"initPrime sucess"<<endl;
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &N, &M);
        init();
        for(int i = 1; i <= N; i++)
        {
            scanf("%d", &a[i]);
            Hash[a[i]] = i;
        }
        int l, r;
        for(int i = 1; i <= M; i++)
        {
            scanf("%d%d", &l, &r);
            query[r].push_back(make_pair(i, l));
        }
        for(int i = 1; i <= N; i++)//枚举左端点,计算对右端点的贡献
        {
            int cnt = 0;
            for(int j = 2 * a[i]; j <= N; j += a[i])//枚举a[i]的倍数
            {
                if(Hash[j] < i) temp[++cnt] = Hash[j];
            }
            sort(temp + 1, temp + cnt + 1);//对位置进行排序,便于从右往左扫
            ll sum = 0;
            temp[0] = 0;
            for(int j = cnt; j >= 1; j--)
            {
                sum += getAns(a[temp[j]] / a[i]) * a[i];
                updateAns(a[temp[j]] / a[i], 1);//更新容斥
                if(j != 1)
                update(temp[j - 1] + 1, temp[j], sum);//更新对左区间的贡献
                else if(temp[1] == 1) update(1, 1, sum);
                else update(1, temp[1], sum);
            }
            for(int j = cnt; j >= 1; j--)
            {
                updateAns(a[temp[j]] / a[i], -1);//还原容斥
            }
            for(int j = 0; j < query[i].size(); j++)//计算结果
            {
                result[query[i][j].first] = get(query[i][j].second);
            }
        }
        for(int i = 1; i <= M; i++)
        {
            printf("%lld\n", result[i]);
        }
    }
    return 0;
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值