题目

题目链接:https://codeforces.com/contest/1559/problem/E
求有多少个长度为 \(n\) 的数组 \(a\) 满足:

  • \(l_i\leq a_i\leq r_i(i\in [1,n])\)
  • \(\sum^{n}_{i=1}a_i\leq m\)
  • \(\gcd(a_1,a_2,\cdots,a_n)=1\)

\(n\leq 50,m\leq 10^5\)

思路

首先很显然可以枚举 \(\gcd=d\),然后问题转化为有多少个序列 \(a\) 满足 \(\left \lceil \frac{l_i}{d} \right \rceil \leq a_i \leq \left \lfloor \frac{r_i}{d} \right \rfloor\)\(\sum^{n}_{i=1}a_i\leq \left\lfloor\frac{m}{d}\right\rfloor\)

直接随便 dp 一下就好了。最后莫反一下就出答案了。
时间复杂度 \(O(nm\log m)\)。如果整除分块的话似乎可以做到 \(O(nm^{\frac{3}{4}})\)

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=55,M=100010,MOD=998244353;
int n,m,a[N],l[N],r[N],prm[M],mu[M];
ll ans,f[M],g[M];
bool v[M];

void findprm(int n)
{
	mu[1]=1;
	for (int i=2;i<=n;i++)
	{
		if (!v[i]) prm[++m]=i,mu[i]=-1;
		for (int j=1;j<=m;j++)
		{
			if (i>n/prm[j]) break;
			v[i*prm[j]]=1; mu[i*prm[j]]=-mu[i];
			if (!(i%prm[j])) { mu[i*prm[j]]=0; break; }
		}
	}
}

int main()
{
	findprm(M-1);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%d%d",&l[i],&r[i]);
	for (int i=m;i>=1;i--)
	{
		f[0]=g[0]=1;
		for (int j=1;j<=m/i;j++)
			f[j]=0,g[j]=1;
		for (int j=1;j<=n;j++)
		{
			int ql=(l[j]-1)/i+1,qr=r[j]/i;
			if (ql>qr) { g[m/i]=0; break; }
			for (int k=0;k<ql;k++) f[k]=0;
			for (int k=ql;k<=m/i;k++)
				f[k]=g[k-ql]-(k>qr ? g[k-qr-1] : 0);
			g[0]=f[0];
			for (int k=1;k<=m/i;k++)
				g[k]=(g[k-1]+f[k])%MOD;
		}
		ans=(ans+mu[i]*g[m/i])%MOD;
	}
	cout<<(ans%MOD+MOD)%MOD;
	return 0;
}