3548. Equal Sum Grid Partition II

This commit is contained in:
2026-03-26 13:33:38 +03:00
parent 3f3bc52106
commit 2cac465442
4 changed files with 294 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>_3548._Equal_Sum_Grid_Partition_II</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,139 @@
var sol = new Solution();
var cases = new (int[][] grid, bool expected, string name)[]
{
(
new[]
{
new[] { 1, 4 },
new[] { 2, 3 }
},
true,
"Example 1"
),
(
new[]
{
new[] { 1, 2 },
new[] { 3, 4 }
},
true,
"Example 2"
),
(
new[]
{
new[] { 1, 2, 4 },
new[] { 2, 3, 5 }
},
false,
"Example 3"
),
(
new[]
{
new[] { 4, 1, 8 },
new[] { 3, 2, 6 }
},
false,
"Example 4"
),
(
new[]
{
new[] { 5, 5, 6, 2, 2, 2 }
},
true,
"Example 5"
),
(
new[]
{
new[] { 100000 },
new[] { 86218 },
new[] { 100000 }
},
true,
"Example 6"
),
(
new[]
{
new[] { 1, 2, 4 },
new[] { 1, 6, 6 },
new[] { 5, 6, 7 }
},
true,
"Example 7"
),
(
new[]
{
new[] { 1, 2, 1, 1, 1 },
new[] { 1, 1, 1, 2, 1 }
},
true,
"Example 8"
),
(
new[]
{
new[] { 1, 1 },
new[] { 2, 1 },
new[] { 1, 1 },
new[] { 1, 2 },
new[] { 1, 1 }
},
true,
"Example 9"
),
(
new[]
{
new[] { 10, 5, 4, 5 }
},
false,
"Example 10"
),
(
new[]
{
new[] { 1, 1 },
new[] { 2, 1 },
new[] { 4, 3 }
},
false,
"Example 11"
),
(
new[]
{
new[] { 4 },
new[] { 3 },
new[] { 4 },
new[] { 4 },
new[] { 4 }
},
false,
"Example 12"
),
(
new[]
{
new[] { 100000 },
new[] { 100000 },
new[] { 100000 },
new[] { 100000 },
new[] { 1 }
},
true,
"Example 13"
),
};
foreach (var (grid, expected, name) in cases)
{
var actual = sol.CanPartitionGrid(grid);
Console.WriteLine($"{name}: {actual} (expected: {expected})");
}

View File

@@ -0,0 +1,130 @@
public class Solution
{
struct SumInfo
{
public long Left;
public long Right;
public long Value;
// ключ - число, значение - индексы, где оно встречается
public Dictionary<long, SortedSet<int>> Nums;
}
public bool CanPartitionGrid(int[][] grid)
{
var height = grid.Length;
var width = grid[0].Length;
var colSums = new SumInfo[width];
var rowSums = new SumInfo[height];
for (var i = 0; i < height; i++)
{
for (var j = 0; j < width; j++)
{
var num = grid[i][j];
colSums[j].Value += num;
rowSums[i].Value += num;
rowSums[i].Nums ??= new Dictionary<long, SortedSet<int>>();
colSums[j].Nums ??= new Dictionary<long, SortedSet<int>>();
rowSums[i].Nums.TryAdd(num, new SortedSet<int>());
rowSums[i].Nums[num].Add(j);
colSums[j].Nums.TryAdd(num, new SortedSet<int>());
colSums[j].Nums[num].Add(i);
}
rowSums[i].Left = rowSums[i].Value;
if (i > 0)
rowSums[i].Left += rowSums[i - 1].Left;
}
for (var i = height - 1; i >= 0; i--)
{
rowSums[i].Right = rowSums[i].Value;
if (i < height - 1)
rowSums[i].Right += rowSums[i + 1].Right;
}
for (var j = 0; j < width; j++)
{
colSums[j].Left = colSums[j].Value;
if (j > 0)
colSums[j].Left += colSums[j - 1].Left;
}
for (var j = width - 1; j >= 0; j--)
{
colSums[j].Right = colSums[j].Value;
if (j < width - 1)
colSums[j].Right += colSums[j + 1].Right;
}
var answer = false;
for (var i = 0; i < rowSums.Length - 1 && !answer; i++)
{
var row = rowSums[i];
var nextRow = rowSums[i + 1];
var diff = Math.Abs(row.Left - nextRow.Right);
var edgesOnly = colSums.Length == 1;
var colLen = height;
if (diff == 0 ||
(row.Left < nextRow.Right && CanDelete(diff, colSums, i, true, i + 1 == rowSums.Length - 1, edgesOnly, colLen)) ||
(row.Left > nextRow.Right && CanDelete(diff, colSums, i, false, i == 0, edgesOnly, colLen)))
answer |= true;
}
for (var j = 0; j < colSums.Length - 1 && !answer; j++)
{
var col = colSums[j];
var nextCol = colSums[j + 1];
var diff = Math.Abs(col.Left - nextCol.Right);
// тут проход по колонкам, значит поиск элемента для удаления надо выполнять по строкам
// если j == 0 и отрезаем в левой части, то надо смотреть только первую и последнюю строку
// если j+1 == colSums.Len-1 и отрезаем в правой части, то надо смотреть только первую и последнюю строку
var edgesOnly = rowSums.Length == 1;
var rowLen = width;
if (diff == 0 ||
(col.Left < nextCol.Right && CanDelete(diff, rowSums, j, true, j + 1 == colSums.Length - 1, edgesOnly, rowLen)) ||
(col.Left > nextCol.Right && CanDelete(diff, rowSums, j, false, j == 0, edgesOnly, rowLen)))
answer |= true;
}
return answer;
}
/// <summary>
///
/// </summary>
/// <param name="target">Целевое число для поиска</param>
/// <param name="infos">Что перебирать</param>
/// <param name="idx">Индекс отсечения (для удаления справа строго больше, для удаления слева включительно)</param>
/// <param name="deleteAfterIdx">Удалять ли спарва от индекса</param>
/// <param name="firstAndLastOnly">Посмотреть только первый и последний SumInfo</param>
/// <param name="edgesOnly">В sumInfo брать только крайние элементы</param>
/// <param name="len">Количество чисел в SumInfo</param>
/// <returns></returns>
private static bool CanDelete(long target, SumInfo[] infos, int idx, bool deleteAfterIdx, bool firstAndLastOnly, bool edgesOnly, int len)
{
for (var i = 0; i < infos.Length; i++)
{
var nums = infos[i].Nums;
if (nums.TryGetValue(target, out var indexes))
{
var before = false;
var after = false;
if (edgesOnly)
{
before = indexes.Contains(0) || indexes.Contains(idx);
after = indexes.Contains(len - 1) || indexes.Contains(idx+1);
}
else
{
before = indexes.GetViewBetween(int.MinValue, idx).Count > 0;
after = indexes.GetViewBetween(idx + 1, int.MaxValue).Count > 0;
}
if ((before && !deleteAfterIdx) || (after && deleteAfterIdx))
return true;
}
// если только границы, перейти сразу к последнему элементу
if (firstAndLastOnly && i == 0)
i = Math.Max(i, infos.Length - 2);
}
return false;
}
}