noip模拟38 a b c



又咕了好几天(我怎么这么能咕(

考场上顺序开题。

\(\mathrm{A.}\mathbb{a}\):不知道

\(\mathrm{B.}\mathbb{b}\):数学

\(\mathrm{C.}\mathbb{c}\):不知道

考试过程:

long long score=0;
begin();
know_the_problem();
while(1)
{
if(!tired()) think_about_the_problem();
else sleep();
}
cout<<score<<endl;


具体可以用以下两句来总结。\(\color{white}(\)

一觉睡,快到了考试时间。

一看题,只会拿暴力骗分。\(\color{white}{)\{}\)

三道题没一道会的。\(\color{white}{\}}\)

于是几乎打满了 \(\mathrm{A}\) 的暴力以及 \(\mathrm{B}\) 的好几个点,\(\mathrm{C}\) 由于没时间了就拿了个 \(1\) 分的点。

估分:\(55+65+1=121\)

实际:\(47+39+1=87\)

还是说正解吧(

A.a

这道题只想出了几个 \(\mathrm{subtask}\) 的分,还有一个推错了。

正解

由于 \(n\) 的范围特别小,因此我们可以暴力枚举这个矩形的上下边界和左边界,用双指针来看右边界的范围,从而确定答案。

时间复杂度:\(O(n^2m)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N=35,M=5e4+5;
int n,m,L,R;
char a[N][M];
int sum[N][M];
long long ans;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
int main()
{
n=read();
m=read();
for(int i=1;i<=n;i++) cin>>(a[i]+1);
L=read();
R=read();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]-'0';
}
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
int l=1,r=1;
for(int k=1;k<=m;k++)
{
l=max(l,k),r=max(r,k);
while(l<=m&&sum[j][l]-sum[i-1][l]-sum[j][k-1]+sum[i-1][k-1]<L) l++;
while(r+1<=m&&sum[j][r+1]-sum[i-1][r+1]-sum[j][k-1]+sum[i-1][k-1]<=R) r++;
if(l>r) break;
ans+=r-l+1;
}
}
}
printf("%lld\n",ans);
return 0;
}


B.b

一开始看到要求多个数的 \(\gcd\),一下就想到了多年前学的懵逼钨丝繁衍莫比乌斯反演。但是觉得部分分太香,就没打正解。

正解

令 \(x=\max\{a\}\)

考虑枚举 \(\gcd\),问题转化为了求 \(\gcd=i\) 的个数。

于是可以运用一个很常见的套路,先求出 \(i\mid\gcd\) 的个数,再利用容斥,算出 \(\gcd=i\) 的个数。

那么此时问题就很好解决了,由于 \(\gcd\) 为 \(i\) 的倍数,则选出来的数应都为 \(i\) 的倍数。设 \(cnt_{i,j}\) 表示第 \(i\) 行有几个数为 \(j\) 的倍数,每一行都可以不选,但不能都不选,因此 \(j\mid\gcd\) 的个数为 \(\displaystyle\prod_{i=1}^n(cnt_{i,j}+1)-1\)

对于容斥的部分,设容斥前的个数(即 \(i\mid\gcd\) 的个数)\(sum_i\),容斥后的个数(即 \(i=\gcd\) 的个数)为 \(ans_i\),不难发现 \(sum_i\) 中只会有 \(\gcd\) 为 \(i\) 的倍数情况,因此我们有式子:


\[sum_i=\displaystyle\sum_{i\mid d}ans_d\]


此时便可以用莫比乌斯反演的式子:


\[F(n)=\displaystyle\sum_{n\mid d}f(d)\Leftrightarrow f(n)=\displaystyle\sum_{n\mid d}\mu(\frac{d}{n})F(d)\]


将 \(F(n)=sum_n,f(n)=ans_n\) 代入可得:


\[ans_i=\displaystyle\sum_{i\mid d}\mu(\frac{d}{i})sum_d\]


此时便可以在 \(O(x\ln x)\) 的时间里完成转移。

由于计算 \(cnt_{i,j}\) 的复杂度为 \(O(nx\ln x)\),计算 \(sum_i\) 的复杂度为 \(O(nx)\),因此不会炸。

code

#include<bits/stdc++.h>
using namespace std;
const int N=25,M=1e5+5,mod=1e9+7;
int tot,mu[M+5],prime[M];
bool flag[M+5];
int n,m,x;
int a[N][M],cnt[N][M],book[N][M];
long long sum[M],ans;
void Mu()
{
mu[1]=1;
for(int i=2;i<=x;i++)
{
if(!flag[i]) prime[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*prime[j]<=x;j++)
{
flag[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else mu[i*prime[j]]=-mu[i];
}
}
}
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
int main()
{
n=read();
m=read();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
a[i][j]=read();
x=max(x,a[i][j]);
book[i][a[i][j]]++;
}
}
Mu();
for(int i=1;i<=x;i++)
{
for(int j=1;i*j<=x;j++)
{
for(int k=1;k<=n;k++) cnt[k][j]+=book[k][i*j];
}
}
for(int i=1;i<=x;i++)
{
sum[i]=1;
for(int j=1;j<=n;j++) sum[i]=sum[i]*(cnt[j][i]+1)%mod;
sum[i]--;
}
for(int i=1;i<=x;i++)
{
long long cal=0;
for(int j=1;i*j<=x;j++) cal=(cal+mu[j]*sum[i*j]%mod)%mod;
ans=(ans+cal*i%mod)%mod;
}
printf("%lld\n",ans);
return 0;
}


C.c

正解

code