提供一篇 O ( n ) O(n) O(n)的题解
首先我们知道,翻转次数的上界是 n + m n+m n+m,因为如果超过 n + m n+m n+m,必有一行或一列翻转了两次,这就做了重复操作。
由此我们想到了 O ( n ∗ m ) O(n*m) O(n∗m)的做法,因为翻转i行j列时黑格个数可以算,是 i ∗ m + j ∗ n − i ∗ j − i ∗ j i*m+j*n-i*j-i*j i∗m+j∗n−i∗j−i∗j(画图有助于理解),枚举 i , j i,j i,j就可以做了。
有没有更快的方法?
根据这个式子我们知道,当 i i i不变, j j j增加 1 1 1时,黑格子个数增加的是定值 n − i − i n-i-i n−i−i,于是我们可以枚举 i i i,判断剩下的 k k k是否能整除 n − i − i n-i-i n−i−i,如果可以,就说明可以组成。
当然,这里剩下的 k k k和 n − i − i n-i-i n−i−i必须同号,如果 k > 0 k>0 k>0但是 n − i − i < 0 n-i-i<0 n−i−i<0的话,加多少都是到不了 k k k的。
---------------代码在这里---------------
#include
#include
#include
#include
using namespace std;
inline int read(){//快读,很快!
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
inline void Yeah(){puts("Yes");exit(0);}//可以得到
int main(){
int n=read(),m=read(),k=read();//读入
for(int i=1;i<=n;++i){
int kk=(k-i*m),a=n-i-i;//kk是剩下的要变成黑色的格子,a是每次增加的黑格子
if(!kk || a && (kk>>31&1)==(a>>31&1) && kk%a==0 && kk/a<=m)Yeah();
//!kk说明已经填完了,可以直接输出。
//a不能等于0,否则%0会报错,
//(kk>>31&1)==(a>>31&1)这句是判断kk和a是否同号,二进制表示的负数第一位是1
//kk%a==0前面已经说过,就是可以组成的情况
//kk/a必须小于等于m,因为j最多加m次
}
puts("No");
return 0;
}
总感觉这题有 O ( 1 ) O(1) O(1)的做法,有兴趣的可以自己思考一下。