using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("3 3");
WillReturn.Add("##.");
WillReturn.Add("#.#");
WillReturn.Add("#..");
//499122178
}
else if (InputPattern == "Input2") {
WillReturn.Add("4 5");
WillReturn.Add("..#..");
WillReturn.Add(".###.");
WillReturn.Add("#####");
WillReturn.Add("..#..");
//598946613
}
else if (InputPattern == "Input3") {
WillReturn.Add("3 4");
WillReturn.Add("#...");
WillReturn.Add(".#.#");
WillReturn.Add("..##");
//285212675
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
const long Hou = 998244353;
static char[,] mBanArr;
static long UB_X;
static long UB_Y;
static void Main()
{
List<string> InputList = GetInputList();
mBanArr = CreateBanArr(InputList.Skip(1));
UB_X = mBanArr.GetUpperBound(0);
UB_Y = mBanArr.GetUpperBound(1);
// 緑の座標のハッシュ値のSet
var GreenHashSet = new HashSet<long>();
for (long X = 0; X <= UB_X; X++) {
for (long Y = 0; Y <= UB_Y; Y++) {
if (mBanArr[X, Y] == '#') {
long Hash = GetHash(X, Y);
GreenHashSet.Add(Hash);
}
}
}
// ノード番号[座標のハッシュ値]なDict
var NodeNoDict = new Dictionary<long, long>();
int NodeNo = 1;
foreach (long EachHash in GreenHashSet) {
NodeNoDict[EachHash] = NodeNo++;
}
var InsUnionFind = new UnionFind();
foreach (long EachNode in NodeNoDict.Values) {
InsUnionFind.MakeSet(EachNode);
}
// ノードの接続
for (long X = 0; X <= UB_X; X++) {
for (long Y = 0; Y <= UB_Y; Y++) {
if (mBanArr[X, Y] != '#') continue;
Action<long, long> ConnAct = (pTargetX, pTargetY) =>
{
if (pTargetX < 0 || UB_X < pTargetX) return;
if (pTargetY < 0 || UB_Y < pTargetY) return;
if (mBanArr[pTargetX, pTargetY] != '#') return;
long Hash1 = GetHash(X, Y);
long Hash2 = GetHash(pTargetX, pTargetY);
long Node1 = NodeNoDict[Hash1];
long Node2 = NodeNoDict[Hash2];
InsUnionFind.Unite(Node1, Node2);
};
ConnAct(X, Y - 1);
ConnAct(X, Y + 1);
ConnAct(X - 1, Y);
ConnAct(X + 1, Y);
}
}
// 代表ノードのSet
var RootSet = new HashSet<long>();
foreach (long EachNode in NodeNoDict.Values) {
RootSet.Add(InsUnionFind.FindSet(EachNode));
}
// 期待値の分子のList
var BunsiList = new List<long>();
for (long X = 0; X <= UB_X; X++) {
for (long Y = 0; Y <= UB_Y; Y++) {
if (mBanArr[X, Y] == '#') continue;
// 4近傍の緑の、代表のSet
var GroupSet = new HashSet<long>();
Action<long, long> AddAct = (pTargetX, pTargetY) =>
{
if (pTargetX < 0 || UB_X < pTargetX) return;
if (pTargetY < 0 || UB_Y < pTargetY) return;
if (mBanArr[pTargetX, pTargetY] != '#') return;
long Hash = GetHash(pTargetX, pTargetY);
long Node = NodeNoDict[Hash];
GroupSet.Add(InsUnionFind.FindSet(Node));
};
AddAct(X, Y - 1);
AddAct(X, Y + 1);
AddAct(X - 1, Y);
AddAct(X + 1, Y);
if (GroupSet.Count == 0) BunsiList.Add(RootSet.Count + 1);
if (GroupSet.Count == 1) BunsiList.Add(RootSet.Count);
if (GroupSet.Count == 2) BunsiList.Add(RootSet.Count - 1);
if (GroupSet.Count == 3) BunsiList.Add(RootSet.Count - 2);
if (GroupSet.Count == 4) BunsiList.Add(RootSet.Count - 3);
}
}
long Answer = 0;
foreach (long EachBunsi in BunsiList) {
Answer += EachBunsi;
Answer %= Hou;
}
Answer *= DeriveGyakugen(BunsiList.Count);
Answer %= Hou;
Console.WriteLine(Answer);
}
// 座標のハッシュ値を返す
static long GetHash(long pX, long pY)
{
return pX * 10000 + pY;
}
////////////////////////////////////////////////////////////////
// IEnumerable<string>をcharの2次元配列に設定する
////////////////////////////////////////////////////////////////
static char[,] CreateBanArr(IEnumerable<string> pStrEnum)
{
var StrList = pStrEnum.ToList();
if (StrList.Count == 0) {
return new char[0, 0];
}
int UB_X = StrList[0].Length - 1;
int UB_Y = StrList.Count - 1;
char[,] WillReturn = new char[UB_X + 1, UB_Y + 1];
for (int Y = 0; Y <= UB_Y; Y++) {
for (int X = 0; X <= UB_X; X++) {
WillReturn[X, Y] = StrList[Y][X];
}
}
return WillReturn;
}
// 引数の逆元を求める
static Dictionary<long, long> mMemoGyakugen = new Dictionary<long, long>();
static long DeriveGyakugen(long pLong)
{
if (mMemoGyakugen.ContainsKey(pLong)) {
return mMemoGyakugen[pLong];
}
return mMemoGyakugen[pLong] = DeriveBekijyou(pLong, Hou - 2, Hou);
}
// 繰り返し2乗法で、(NのP乗) Mod Mを求める
static long DeriveBekijyou(long pN, long pP, long pM)
{
long CurrJyousuu = pN % pM;
long CurrShisuu = 1;
long WillReturn = 1;
while (true) {
// 対象ビットが立っている場合
if ((pP & CurrShisuu) > 0) {
WillReturn = (WillReturn * CurrJyousuu) % pM;
}
CurrShisuu *= 2;
if (CurrShisuu > pP) return WillReturn;
CurrJyousuu = (CurrJyousuu * CurrJyousuu) % pM;
}
}
}
#region UnionFind
// UnionFindクラス
internal class UnionFind
{
private class NodeInfoDef
{
internal long ParentNode;
internal long Rank;
}
private Dictionary<long, NodeInfoDef> mNodeInfoDict =
new Dictionary<long, NodeInfoDef>();
// 要素が1つである木を森に追加
internal void MakeSet(long pNode)
{
NodeInfoDef WillAdd = new NodeInfoDef();
WillAdd.ParentNode = pNode;
WillAdd.Rank = 0;
mNodeInfoDict[pNode] = WillAdd;
}
// 合併処理
internal void Unite(long pX, long pY)
{
long XNode = FindSet(pX);
long YNode = FindSet(pY);
long XRank = mNodeInfoDict[XNode].Rank;
long YRank = mNodeInfoDict[YNode].Rank;
if (XRank > YRank) {
mNodeInfoDict[YNode].ParentNode = XNode;
}
else {
mNodeInfoDict[XNode].ParentNode = YNode;
if (XRank == YRank) {
mNodeInfoDict[YNode].Rank++;
}
}
}
// ノードを引数として、木の根を取得
internal long FindSet(long pTargetNode)
{
// 根までの経路上のノードのList
var PathNodeList = new List<long>();
long CurrNode = pTargetNode;
while (CurrNode != mNodeInfoDict[CurrNode].ParentNode) {
PathNodeList.Add(CurrNode);
CurrNode = mNodeInfoDict[CurrNode].ParentNode;
}
// 経路圧縮 (親ポインタの付け替え)
foreach (long EachPathNode in PathNodeList) {
mNodeInfoDict[EachPathNode].ParentNode = CurrNode;
}
return CurrNode;
}
internal void DebugPrint()
{
foreach (var EachPair in mNodeInfoDict.OrderBy(pX => pX.Key)) {
Console.WriteLine("mNodeInfoDict[{0}].ParentNode={1}",
EachPair.Key, EachPair.Value.ParentNode);
}
}
}
#endregion