hdu6102 容斥+树状数组 2017多校第六场1007

版权声明:编写不易,转载请注明出处,谢谢。 https://blog.csdn.net/WilliamSun0122/article/details/77162685

题意
给你n个数,这n个数是1-n的一个排列。再有m次询问,每次询问一个区间[l,r]的价值。区间价值定义为 sumri=lsumrj=i+1sumrk=j+1gcd(sz[i],sz[j]==sz[k])sz[k]

题解
这道题关键就是怎么快速求一个区间中有多少个满足条件的三元对。
我是看题解做出来的,具体思维过程不太清楚。
这道题解法就是枚举右端点,算左端点的贡献,贡献值用树状数组维护。
固定右端点sz[i],枚举在它左边的它的倍数sz[j],sz[k]和sz[z](假设顺序为sz[z],sz[k],sz[j],sz[i]),如果gcd(sz[j]/sz[i],sz[k]/sz[i])==1,那么就在贡献区间[z+1,k]中加sz[i]。现在问题主要就是如何快速知道一个数的倍数中(比如9,6,11,5,8,3,2,其中x代表sz[i]的x倍)一个倍数(比如9)的右边有多少个倍数与之互质。这个要用容斥来计算。
关于容斥
http://blog.csdn.net/williamsun0122/article/details/77161256
关于树状数组(了解到一维区间更新,单点查询即可)
http://blog.csdn.net/williamsun0122/article/details/71499404
具体过程可看代码注释

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;

const int INF = 0x3f3f3f3f;
const int maxn = 1e5+5;
//sz[i]表示第i个位置代表的数,v[i]表示i这个数所在的位置
//tmp[]储存固定右端点数后在其左边的倍数的位置,num是其数量
int sz[maxn],tmp[maxn],v[maxn],n,m,l,r,num;
ll c[maxn]; //表示贡献
//代表容斥的状态,first代表因子的乘积,second代表符号
vector<P> state[maxn]; 
//prime[i]表示i的质因子
vector<int> prime[maxn];
bool vis[maxn];  //用于筛出质因子
//用于做容斥判断,ie[i]表示质因子i在右边出现的次数
int ie[maxn];  
//用于存放询问,q[i]为固定右端点为i的询问
//其first代表第几个询问,second代表询问的左端点
vector<P> q[maxn];
//ans[i]代表第i个询问的答案
ll ans[maxn];

void init() //筛出每个数所有容斥的状态
{
    memset(vis,true,sizeof(vis));
    for(int i=0;i<maxn;i++)
    {
        prime[i].clear();
        state[i].clear();
    }
    for(int i=2;i<maxn;i++)//找出每个数的质因子
    {
        if(vis[i])
        {
            for(int j=i;j<maxn;j+=i)
            {
                prime[j].push_back(i);
                vis[j] = false;
            }
        }
    }
    for(int i=2;i<maxn;i++)//求出每个数容斥的状态
    {
        for(int j=0;j<(1<<prime[i].size());j++)
        {
            state[i].push_back(make_pair(1,1));
            for(int k=0;k<prime[i].size();k++)
            {
                if((j>>k)&1)
                {
                    state[i][j].first *= prime[i][k];
                    state[i][j].second *= -1;
                }
            }
        }
    }
}

void add(int x,int value) //更新容斥状态
{
    for(int i=0;i<state[x].size();i++)
    {
        ie[state[x][i].first] += value;
    }
}

int get(int x) //得到每个倍数左边有多少个倍数与之互质
{
    int res = 0;
    for(int i=0;i<state[x].size();i++)
    {
        res += ie[state[x][i].first]*state[x][i].second;
    }
    return res;
}

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

void update(int x,ll value) //更新贡献
{
    while(x<=n)
    {
        c[x] += value;
        x += lowbit(x);
    }
}

ll query(int x)  //得到贡献
{
    ll res=0;
    while(x>0)
    {
        res += c[x];
        x -= lowbit(x);
    }
    return res;
}

int main()
{
    int t;
    init();
    scanf("%d",&t);
    while(t--)
    {
        memset(v,INF,sizeof(v));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&sz[i]);
            v[sz[i]] = i;
            q[i].clear();
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&l,&r);
            q[r].push_back(P(i,l));
        }
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++)
        {
            num=0;
            memset(ie,0,sizeof(0));
            for(int j=2*sz[i];j<maxn;j+=sz[i])
            {
                if(v[j]<i) tmp[++num] = v[j];
            }
            sort(tmp+1,tmp+num+1);
            tmp[0]=0;
            ll sum = 0;
            for(int j=num;j>0;j--)
            {
                sum += get(sz[tmp[j]]/sz[i])*sz[i];
                //更新容斥
                add(sz[tmp[j]]/sz[i],1);
                //更新贡献区间
                update(tmp[j-1]+1,sum);
                update(tmp[j]+1,-sum);
            }
            //还原容斥
            for(int j=num;j>0;j--) add(sz[tmp[j]]/sz[i],-1);
            //固定i为右端点的询问
            for(int j=0;j<q[i].size();j++) ans[q[i][j].first] = query(q[i][j].second);
        }
        for(int i=1;i<=m;i++)
        {
            printf("%lld\n",ans[i]);
        }
    }
    return 0;
}

这道题要对树状数组和容斥都有一定了解之后才能做。不是很好说清楚,如果大家一遍看不懂建议多看几遍(最好先看几道容斥和树状数组的题)。

猜你喜欢

转载自blog.csdn.net/WilliamSun0122/article/details/77162685