题意:
有n个询问,每次询问输入五个数a,b,c,d,k;每次求gcd(i,j)==k的对数,i∈[a,b] j∈[c,d]
思路:
首先我们将上面的文字进行公式化得到要求的即是
中括号为括号内条件成立为1,否则为0
那么我们可以进行容斥将其转换一下为
那么我们只需要解决一个问题即如何计算区间1->x,1->y内的gcd(i,j)==k的对数即可。
在这里我们用到了一个转化,满足gcd==k,那么我们可以转化为满足gcd==1,此时定义域也要对应的发生改变 这里的除法为向下取整
简单的理解为,将定义域缩小后如果满足gcd==1,那么将该数扩大k倍,则必定满足gcd==k,还有定理有关证明, 在此简单说明。
此时问题转化为
为单位函数,
此时运用到莫比乌斯反演的转化有卷积公式
那么转化为 ,我们发现在此式子的基础上可以进行更进一步的优化,
我们可以优先的进行枚举gcd的因子d,然后进行求和即:
,此式子的含义为,枚举因子d,然后考虑之前的式子,d要满足是gcd的因子,那么对于后面的求和式,只要满足i,j里面有d这个因子即可,即d能整除i,此时我们发现对于后两个求和式子可以简单的发现,将其转化为
那么此时对于后面的x/k/d*y/k/d我们可以用整除分块来做,可以将其优化到的复杂度。
整除分块(这里的/都为向下取整):
我们可以知道对于一个数i,我们都能找到至少一个j(j可能==i)使之x/i=x/j;
这表示在i->j之间与x的商为同一个值,那么我们要加速这个过程肯定想要找到最大的maxj,使
x/i=x/maxj;这里有一个结论为最大的maxj为x/(x/i)。
综上,到此为止,此题基本已经得出结论即求每一个
,用整除分块进行加速,然后进行容斥既可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+5;
int mo[N],prime[N],vis[N];
void pre(){
mo[1]=1;
int cnt=0;
for(int i=2;i<=N;i++){
if(!vis[i])prime[++cnt]=i,mo[i]=-1;
for(int j=1;j<=cnt&&1ll*prime[j]*i<=N;j++){ //注意别爆int
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
else mo[i*prime[j]]=-mo[i];
}
}for(int i=1;i<=N;i++)mo[i]=mo[i-1]+mo[i];
}
int cal(int x,int y){
int dd;
int ans=0;
for(int d=1;d<=min(x,y);d=dd+1){
dd=min(x/(x/d),y/(y/d)); //求出最大的maxj
ans+=(mo[dd]-mo[d-1])*(x/d)*(y/d); //这段区间的μ值和
}return ans;
}
void solve(){
int a,b,c,d,k;
cin>>a>>b>>c>>d>>k;
cout<<cal(b/k,d/k)-cal((a-1)/k,d/k)-cal(b/k,(c-1)/k)+cal((a-1)/k,(c-1)/k)<<"\n";
}
int main(){
int t;
cin>>t;
pre();
while(t--){
solve();
}
return 0;
}
D. Yet Another Minimization Problem
思路:
我们可以将对应位置上的ai和bi进行交换然后最小化一个值,
首先(a+b)*(a+b)=a*a+b*b+2*a*b,对于一个位置i上的值ai的贡献为自身平方的n-1倍,和自身与其他数进行乘积的2倍之和,假设有四个数a,b,c,d;
贡献为(n-2)*(a*a+b*b+c*c+d*d)+a*(a+b+c+d)+b*(a+b+c+d)+c*(a+b+c+d)+d*(a+b+c+d)
简单化简(a+b+c+d)*(a+b+c+d),前方的平方和贡献不变,那么此时假设数组a的和为x,数组b的和为y,那么就是最小化x*x+y*y,通过不等式的知识可以得知
当且仅当x==y的时候等号成立,那么我们此时就需要尽量的满足x,y尽量接近,换句话说就是x,y尽量接近(x+y)/2的值。
这个问题通过观察题目的数据范围发现可以通过简单的双重循环记录一下即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=105;
int a[N],b[N];
int dp[2][20005];
void solve(){
int n;
cin>>n;
ll ans=0,sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
ans+=a[i]*a[i];
}for(int i=1;i<=n;i++){
cin>>b[i];
sum+=b[i];
ans+=b[i]*b[i];
}if(n==1){
cout<<"0\n";
return ;
}
for(int i=0;i<sum;i++)dp[1][i]=dp[0][i]=0;
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<sum;j++){
if(dp[0][j]==1){
if(j+a[i]<sum)dp[1][j+a[i]]=1;
if(j+b[i]<sum)dp[1][j+b[i]]=1;
}
}for(int j=0;j<sum;j++){
dp[0][j]=dp[1][j],dp[1][j]=0;
}
}int p=sum/2;
while(dp[0][p]!=1){
p--;
}sum-=p;
cout<<p*p+sum*sum+(n-2)*ans<<"\n";
}
int main(){
int t=1;
cin>>t;
while(t--){
solve();
}
}