思想
一共就会有四种情况:
我们用a[i]统计每一行开灯的概率,b[i]统计每一行关灯的概率。
c[i]统计每一列开灯的概率,d[i]统计每一列关灯的概率。
sum[i]用来统计所有列开灯的概率,ans[i]用来统计所有列关灯的概率。
因为所有灯泡的默认状态都是关闭状态,所以一个灯泡如果被翻了两次或者没被翻过最终状态都是关闭状态。
如图,同一个位置被1行翻了一次,被列翻了一次就会变为关闭状态:
所以a[i]要和ans[i]对立,b[i]要和sum[i]对立,这样保证最终状态是开灯状态。
code
#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int N=1e6+10; // 定义一个足够大的数组大小
const int mo=998244353; // 定义模数
int x,y,n,sum,m,k,z,a[N],b[N],c[N],d[N],key; // 定义变量
// 快速幂算法,用于计算 a^b % mo
ll q_pow(ll a,ll b){
ll res=1;
while(b){
if(b&1)res=res*a%mo; // 如果 b 是奇数,则将 res 乘以 a 并取模
a=a*a%mo; // 将 a 的平方取模
b>>=1; // 将 b 右移一位,相当于除以 2
}
return res;
}
// 求逆元,用于计算 a 的模逆元,即 a^(-1) % mo
ll inv(ll a){
return q_pow(a,mo-2); // 根据 Fermat's Little Theorem,a^(mo-2) 即为 a 的模逆元
}
// 主函数
signed main()
{
int t=1; // 测试用例的数量
while(t--){
cin>>n; // 输入网格的行数和列数
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>x;
b[i]=(x-a[i])*inv(x)%mo; // 计算没开灯的概率
a[i]=a[i]*inv(x)%mo; // 计算开灯的概率
}
for(int i=1;i<=n;i++)
{
cin>>c[i]; // 输入每列的初始
}
int ans=0,sum=0; // 初始化没开灯和开灯的总概率
for(int i=1;i<=n;i++)
{
cin>>x;
d[i]=(x-c[i])*inv(x)%mo; // 计算没开灯的概率
c[i]=c[i]*inv(x)%mo; // 计算开灯的概率
ans=(ans+d[i])%mo; // 更新没开灯的总概率
sum=(sum+c[i])%mo; // 更新开灯的总概率
}
for(int i=1;i<=n;i++)
{
z=(z+a[i]*ans%mo+b[i]*sum%mo)%mo; // 计算最终答案
}
cout<<z; // 输出最终答案
}
return 0;
}