前言
虽然我去年就已经学过这东西了,但是一直没有整理、归纳。而且我发现我对它的套路不够熟悉、对它的理解不够透彻,卒致GDOIDay2T1没切(详细事故记录戳这里)。所以,在此做一个知识整合。
引子
先来看bzoj的一道题:Problem b。
Problem
给出n组询问,每组询问给出a、b、c、d、k五个整数,求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k。
Hint
100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
Solution
首先,考虑转化一下。既然
(
为克罗内克函数),那么其实我们可以给a、b、c、d同除以一个k,转化为求
。
考虑设
,则原问题的答案=
。
于是原问题转化为求解
。
对于每个询问,都可以用
的时间复杂度求出Ans数组,然后计算Ans。
然而,这依然不尽人意。
考虑设
,先保证n<m,否则交换。
直接计算f(k)很难。
那么,再设
,即满足k为(x,y)的约数的数对(x,y)的个数,则
。
g(k)的求解很容易,符合
的数对(x,y)的个数即为答案。所以
。
然而,我们虽然貌似知道了很多,实际上并没有什么卵用。还有一个至关重要的难题亟待解决:如何用g(k)求解f(k)呢?
这时,莫比乌斯反演闪亮登场!
反演
反演到底是什么?
我觉得这篇博客讲得十分详尽。大意如下:
对于一个数列{f}来说,如果我们知道另外一个数列{g},满足如下条件:
反演(inversion)就是利用{g}来表示出{f}:
也就是说,反演的原理就是容斥。
下面的“莫比乌斯反演的引入”实质上就是反演。当然,如果你做的莫比乌斯反演的题多了,你会发觉,不仅是它,所有的莫比乌斯反演题的解法实质上都是反演。
莫比乌斯反演的引入
由于这段东西过于重要,所以不得不写一下。
但是,由于这段东西又过于冗长,所以我在网上截了两张图。
声明:以下两张图片截图自百度百科。
莫比乌斯函数
定义
为自变量为n(
)时的莫比乌斯函数。若设
,其中
为n的质因子,则
的取值如下:
也就是说,当n=1时,
;当n有重复质因子时,
;当n无重复质因子时,
。
因此,
为积性函数,可通过线筛顺带
求出。
那么,如何用这个函数求解上面那题呢?
性质1:
这个怎么证明呢?
依然设
,再设
。显然,若要d≠0,则须满足
。设有q个bi为1,其余均为0,则可推出:
这和组合数扯上关系了。可以观察杨辉三角推出性质1,当然,我们也可以更严谨地,通过(被苹果砸死的)牛顿提出的二项式定理推出性质1。
性质2:
是积性函数。
这个,看一看积性函数的定义,很显然,就不解释了。
莫比乌斯反演的证明
下面的内容也比较重要,但是非常冗长,故贴上两张图片instead of手打:
回到引子
重温一下,我们已知下式:
现在我们要通过g(k)求出f(k)。
通过莫比乌斯反演的变形可知:
设T=k*d,则上式可以变得简洁些:
于是,对于每个询问,我们可以用
的时间复杂度求出f。
但还是过不了。
这时,我们考虑祭上莫比乌斯反演的又一强力套路:
分块
首先,我们知道,
这东西是只有
这
种取值的;同理,
也只有
种取值。
想象在一条直线上,
和
的每一种取值都映射成该条直线上的一点,那么至多会有
个点。以最左最右两点取闭区间,则这个闭区间可视作一条线段,且此线段被它身上的
个点分成
条子线段。
这些子区间内的
和
的取值都是分别相同的,所以可以考虑一起计算。
那么,考虑预处理出
的前缀和,然后对于每种不同的
和
段分别计算一下即可。
于是,计算f的时间复杂度就被缩减为
。
例题
当然,除过bzoj的Problem b,还是有不少简单的莫比乌斯反演题滴。
bzoj上的DZY loves Math(也即jzoj上的Wyx)、bzoj上的于神之怒加强版(也即jzoj上的于神之怒)都是比较经典的莫比乌斯反演,套路大致相同。
山东省选2015第一轮第一试的约数个数和(英文divisor,jzoj上也有)则是一道比较有思维难度的题,还要转换一下式子。
特点
- 题目大意都比较简洁明了
- 要求的答案都和gcd脱不了干系
- 画风比较诡异
- 要推很多很多、很长很长的式子,因而会蚕食你一页多的草稿纸
- 一般出现在省选Day2T1
要点
- 第零步:牢记莫比乌斯反演的几种变化形式
- 第一步:将含有gcd的项移到后面,利用莫比乌斯函数的性质1将其转化成
- 第二步:将含有 的项移到前面,突破僵局,利用等差数列等公式拆掉后面的几个
- 第三步:看到式子被化简成一重循环的时候,就可以上分块了
压轴大戏——谈笑风生
不要以为我要谈笑风生,我说的是GDOIDay2T1。
Problem
给定一个N个点、M条边的图,点i的点权为a[i]。对于边 ,其边权 ,即经过这条边要花len秒。小熊维尼一开始在点1,他想在T秒内走到点n,而他能花P点能量使所有边的边权减少P且不能减至0。求最少需要花费多少能量才能在T秒内到达,以及在此前提下,最少要花费多少秒。
Hint
Solution
比赛时我看了一会,发现这道题是两道题拼起来的题:求解边权+二分spfa。
那么,二分spfa谁都会打,关键就在于求解边权了。
However,在比赛时我并没有想到正解,甚至连莫比乌斯反演也没有想多久。
下面现场推一波式子:
设a[u]<a[v],不满足则交换。
根据莫比乌斯函数的性质1,可将
这个东西改换一下:
考虑将后面的
挪到前面,然后用i、j枚举x的倍数:
瞄到后面有一个x,果断将其挪到前面:
考虑将后面的i、j分开处理,分别计算它们出现的次数,于是可以推出下式:
运用等差数列求和公式可以拆掉后面的两个
:
我们发现后面要相加的冗长式子有公因式,于是合并同类项:
前面的
可以预处理前缀和。
后面的式子,我们发现它的取值只与
和
有关,而它们至多只有
种不同取值,于是可以分块处理。
时间复杂度:线筛求
,预处理前缀和
,计算每条边边权
,二分最短路
。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e4+1,M=N<<2,A=1e5;
int i,j,mu[A+1],cnt,prime[A+1],x;
bool p[A+1];
ll sum[A+1];
void init()
{
memset(p,1,sizeof p);
mu[1]=sum[1]=1;
fo(i,2,A)
{
if(p[i]){prime[++cnt]=i;mu[i]=-1;}
for(j=1;j<=cnt&&(j==1||i%x)&&i*(x=prime[j])<=A;j++)
{p[i*x]=0;mu[i*x]=i%x?-mu[i]:0;}
sum[i]=sum[i-1]+(ll)mu[i]*i;
}
}
int n,m,a[N],u,v;
ll T,Len;
void Mobius(ll n,ll m)
{
if(n>m)swap(n,m);
ll i=1,j,ni,mi;Len=0;
while(i<=n)
{
j=min(n/(ni=n/i),m/(mi=m/i));
Len+=(sum[j]-sum[i-1])*ni*mi*(2+ni+mi)>>1;
i=j+1;
}
}
int tot,tov[M],next[M],last[N];
ll len[M],maxP;
inline void link(int x,int y)
{
tov[++tot]=y;
len[tot]=Len;
next[tot]=last[x];
last[x]=tot;
}
void scan()
{
scanf("%d%d%d",&n,&m,&T);
fo(i,1,n)scanf("%d",&a[i]);
if(n==1){printf("0 0");exit(0);}
fo(i,1,m)
{
scanf("%d%d",&u,&v);
Mobius(a[u],a[v]);
maxP=max(maxP,Len);
link(u,v);
link(v,u);
}
}
int y,head,tail,que[A*10];
ll dis[N];
bool exist[N];
inline ll calc(ll P){return max(len[i]-P,0ll);}
ll spfa(ll P)
{
memset(dis,127,sizeof dis);
head=dis[que[tail=1]=1]=0;
memset(exist,0,sizeof exist);exist[1]=1;
while(head<tail)
{
x=que[++head];
for(i=last[x];i;i=next[i])
if(dis[y=tov[i]]>dis[x]+calc(P))
{
dis[y]=dis[x]+calc(P);
if(!exist[y])
{
exist[que[++tail]=y]=1;
if(dis[que[head+1]]>dis[y])swap(que[head+1],que[tail]);
}
}
exist[x]=0;
}
return dis[n];
}
void Bsort()
{
ll l=0,r=maxP,mid;
while(l<r)
{
mid=l+r>>1;
if(spfa(mid)<=T)
r=mid;
else l=mid+1;
}
printf("%lld %lld",l,spfa(l));
}
int main()
{
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
init();
scan();
Bsort();
}