题目链接

A:简:https://vjudge.net/contest/395157#problem/A
B:稍难:https://vjudge.net/contest/395157#problem/B

题目大意

A:给m,n求m * n的网格能有多少个矩形,2 * 3与3 * 2算一个。
B:给m,n求m * n的网格有多少个正方形,例如:4 * 3的网格中1 * 1的正方形有12个,2 * 2的正方形有6个,3 * 3的正方形有2个。
A题只关心长宽,长宽相同就算一个,B题任意一个正方形就算是一个。

解题思路

A:找规律。看看数据规模,m,n都是1e9,不出意外应该无法靠循环累加求出,所以直接找最终公式输出。
以4 * 3的举例:
图片说明
我们要算的是黑色的个数,而黄色的是与黑色部分有重复的。把黑色部分划分成两部分求,第一部分是上面的三角形,第二部分是下面的矩形。三角形所含黑色个数为3+2+1,一般化之后为m和n中较小的那个 x+(x-1)+(x-2)+(x-3)+……+1=x*(x+1)/2;矩形所含黑色个数为1 * 3,一般化之后为m与n差的绝对值 * m和n中较小的那个。两部分相加。
B:可忽略括号内容(根据题目举的例子,容易推出1 * 1有12个,即4 * 3=12;2 * 2有6个,即3 * 2=6;3 * 3有2个,即2 * 1=2。行数列数每次减一相乘再相加。假设m=max{m,n},n=min{m,n},答案为 (m*n)+((m-1)*(n-1))+……+((m-n+1)*1),要是循环相乘累加,实现很简单,但是数据规模不允许,所以我把式子去括号试试, (n*m)+(n*m-n-m+1)+(n*m-2*n-2*m+4)+(n*m-2*n-2*m+9)+……+(n*m-(n-1)*n-(n-1)*m+(n-1)^2)=n*(m*n)-n*(n-1)*(m+n)/2+(n-1)*n*(2*n-1)/6 “这里用到平方和公式与等差求和公式”
但是,不幸的消息,AC不了,25数据过了19,应该是实现有点问题,可是debug了好久也没找到错哪,所以换个思路。)
把这个m * n的网格同A题一样分成上部分与下部分,上部分是正方形,下部分是矩形,只在上部分形成的正方形的个数为 n*n+(n-1)*(n-1)+(n-2)*(n-2)+……+1*1,由平方和公式得, n*(n+1)*(2*n+1)/6;根据下半部分矩形形成的正方形(即存在位于矩形部分的正方形) (m-n)*n+(m-n)*(n-1)+……+(m-n)*1=(m-n)*n*(n+1)/2
因此这两部分的和就是最终答案,仔细一看会发现两部分式子的公因式 n*(n+1)/2,可化为 n*(n+1)/2 * ((m-n)+(2*n+1)/3)
最值得深思的事情来了。

cout<<(n*(n+1)/2%mod*((m-n)+(2*n+1)/3)%mod)%mod<<endl;
//or
cout<<((n*(n+1)%mod*(2*n+1)/6)%mod+((m-n)*n%mod*(n+1)/2)%mod)%mod<<endl;
//为什么无法AC
//第一个输出:2*n+1不一定能够整除3,无法整除却除了,必然影响结果。
//第二个输出:n*(n+1)*(2*n+1)必然可以整除6,但是先对n*(n+1)取模后再与2*n+1相乘就不一定能整除6了,也可能影响结果。

这个问题的解决十分巧妙,也是这个题的难点。
直接在代码里讲解吧。

AC代码

//A
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll m,n;
int main(){
    cin>>m>>n;
    if(n>m) swap(m,n);
    cout<<(n*(m-n)%mod+n*(n+1)/2%mod)%mod<<endl;
} 

//B
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll m,n;
int main(){
    cin>>m>>n;
    if(n>m) swap(n,m); 
    ll ans=n*(n+1)/2;//公因式
    if(ans%3==0){//要是公因式能整除3,直接让公因式整除了3,除去后顾之忧,这样实现了n*(n+1)/6
        ans/=3;//进行除3操作
        ans%=mod;//及时取模
        ans=ans*(3*m-n+1)%mod;//原公式为n*(n+1)/2 * ((m-n)+(2*n+1)/3)除公因式的部分提取出3,还剩3*m-n+1。
    }
    else{//公因式无法整除3,我们就让2*n+1整除3。n*(n+1)与2*n+1中至少有一个能整除3,既然n*(n+1)无法整除3,说明2*n+1可以整除3。
        ans%=mod;//及时取模
        ans=ans*(m-n+(2*n+1)/3)%mod;//与原公式相同
    }
    cout<<ans<<endl;
} 

总结

数学题,但是还挺有技巧的,不是特别好想。