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\) 的倍数情况,因此我们有式子:
此时便可以用莫比乌斯反演的式子:
将 \(F(n)=sum_n,f(n)=ans_n\) 代入可得:
此时便可以在 \(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

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号