AT3555 fLIP【题解】

提供一篇 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(nm)的做法,因为翻转i行j列时黑格个数可以算,是 i ∗ m + j ∗ n − i ∗ j − i ∗ j i*m+j*n-i*j-i*j im+jnijij(画图有助于理解),枚举 i , j i,j i,j就可以做了。

AT3555 fLIP【题解】_第1张图片

有没有更快的方法?

根据这个式子我们知道,当 i i i不变, j j j增加 1 1 1时,黑格子个数增加的是定值 n − i − i n-i-i nii,于是我们可以枚举 i i i,判断剩下的 k k k是否能整除 n − i − i n-i-i nii,如果可以,就说明可以组成。

当然,这里剩下的 k k k n − i − i n-i-i nii必须同号,如果 k > 0 k>0 k>0但是 n − i − i < 0 n-i-i<0 nii<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)的做法,有兴趣的可以自己思考一下。

你可能感兴趣的:(题解,题解)