Loading

NOIP 模拟 $15\; \text{夜莺与玫瑰}$

题解

一道很妙的题,让求对于一个矩阵中,两点相连成线,有多少条直线,他们的交集是有限集。

转化一下题目,发现水平和竖直的只有 \(n+m\) 条,而左斜和右斜的条数是相同的,所以我们只需求出左或右中的即可

这个矩阵中一共有 \(\sum_{a=1}^{n-1}\sum_{b=1}^{m-1}[gcd(a,b)=1]\) 条斜率不同的直线,那么对于每一种斜率,又有 \((n-a)×(m-b)\) 个点
可以伸出来,但是会有重复的所以要减去 \((n-2×a)×(m-2×b)\)

所以最后可以推出来一个总式子

\[\sum_{a=1}^{n-1}\sum_{b=1}^{m-1}[gcd(a,b)=1](n-a)×(m-b)-\max(n-2×a,0)×\max(m-2×b,0) \]

\(60pts\) 到手

考虑如何优化它,这里提供一种 \(O(n)\) 查询的做法。

我们维护三个数组 \(\gcd\) \(\rm gnm\) \(\rm gsm\)\(\rm gnm_{i,j}\) 表示从 \(1~j\) 有多少个和 \(i\) 互质的数,\(\rm sum\) 表示就是这些数的和。

对于 \(\rm gcd\) 我们可以 \(n^2\) 预处理

查询时,我们枚举 \(a\),对于后边的式子,拆一下

\[(n-a)×m-(n-a)×b-(n-2×a)×m+(n-2×a)×2×b \]

这个式子,根据我们维护的三个数组就可以求出来了。

Code
#include<bits/stdc++.h>
#define ri register int
#define p(i) ++i
using namespace std;
namespace IO{
    char buf[1<<21],*p1=buf,*p2=buf;
    #define gc() p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++
    template<typename T>inline void read(T &x) {
        ri f=1;x=0;register char ch=gc();
        while(ch<'0'||ch>'9') {if (ch=='-') f=0;ch=gc();}
        while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
        x=f?x:-x;
    }
}
using IO::read;
namespace nanfeng{
    #define int unsigned int
    #define cmax(x,y) ((x)>(y)?(x):(y))
    #define cmin(x,y) ((x)>(y)?(y):(x))
    #define FI FILE *IN
    #define FO FILE *OUT
    typedef long long ll;
    static const int N=4e3+7,MOD=(1<<30)-1;
    int gsm[N][N],T,ans,n,m;
    short num[N][N],*gcd[N];
    inline void init() {
        ri n=N-7;
        // int *(gcd[N])=new int[n+7];
        gcd[0]=new short[n+7];
        for (ri i(1);i<=n;p(i)) {
            gcd[i]=new short[n+7];
            gcd[i][i]=gcd[i][0]=gcd[0][i]=i;
            // printf("i=%d\n",i);
            for (ri j(1);j<i;p(j)) 
                gcd[j][i]=gcd[i][j]=gcd[j][i%j];    
        }
        delete gcd[0];
        for (ri i(1);i<=n;p(i)) {
            for (ri j(1);j<=n;p(j)) {
                if (gcd[i][j]==1) gsm[i][j]=j,num[i][j]=1;
                gsm[i][j]+=gsm[i][j-1];
                num[i][j]+=num[i][j-1];
            }
            delete gcd[i];
        }
    }
    inline int main() {
        // FI=freopen("nanfeng.in","r",stdin);
        // FO=freopen("nanfeng.out","w",stdout);
        init();
        read(T);
        for (ri z(1);z<=T;p(z)) {
            read(n),read(m);ans=0;
            ri bs=n*m;
            for (ri i(1);i<=n-1;p(i)) {
                ans=(ans+(int)num[i][m-1]*(n-i)*m);
                ans-=(n-i)*gsm[i][m-1];
                if (i*2>=n) continue;
                int lm=(m&1)?m/2:m/2-1;
                ans-=(n-2*i)*(m*num[i][lm]-(gsm[i][lm]<<1));
            }
            printf("%u\n",(ans*2+n+m)&MOD);
        } 
        return 0;
    }  
    #undef int
}
int main() {return nanfeng::main();} 

小技巧,这个模数很特殊,可以自然溢出,减少低效的取模

posted @ 2021-07-16 10:15  ナンカエデ  阅读(46)  评论(0编辑  收藏  举报