版权声明:编写不易,转载请注明出处,谢谢。 https://blog.csdn.net/WilliamSun0122/article/details/77162685
题意
给你n个数,这n个数是1-n的一个排列。再有m次询问,每次询问一个区间[l,r]的价值。区间价值定义为
题解
这道题关键就是怎么快速求一个区间中有多少个满足条件的三元对。
我是看题解做出来的,具体思维过程不太清楚。
这道题解法就是枚举右端点,算左端点的贡献,贡献值用树状数组维护。
固定右端点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;
}
这道题要对树状数组和容斥都有一定了解之后才能做。不是很好说清楚,如果大家一遍看不懂建议多看几遍(最好先看几道容斥和树状数组的题)。