这是一道动态规划题。
首先来分析:题干中对于桥梁的限制,其实是在说:某个点不能与自己集合中的点相连,也不能同时与同一个集合中的两个点相连,即题目下方示例2中那两种不合法的连接方法。也就是说,对于红色点A来说,它可以与蓝色集合中至多一个点相连;同时,也可以与紫色集合中至多一个点相连。它是否与蓝色集合中的点相连并不影响其与紫色集合的点相连,即桥梁搭建的全部情况,应该等于红蓝相连的情况数×红紫相连的情况数×蓝紫相连的情况数。因为不论红紫、蓝紫之间如何相连,红蓝之间相连的情况数跟它们均无关。
现在我们把问题由三个集合简化到了两个集合。这两个集合之间的点相连接需要满足刚才那两个条件:
- 集合内部的点不能相连
- 集合A中的一个点要么与集合B中的一个点相连,要么就不连接集合B中的任何点
第二个条件其实就是动态规划的转移方程了。对于集合A中的第I个点:
- 第一种情况,它不跟集合B中的任何一个点相连。此时即变为一个子问题:集合A中的i-1个点与集合B中的j个点搭建桥梁;
- 第二种情况,他跟集合B中的某一个点相连。连接方法共有j种,连接后变为另一个子问题:集合A中剩余的i-1个点与集合B中剩余的j-1个点相连。
因此,转移方程为:
dp[i, j] = dp[i - 1, j] + dp[i - 1, j - 1] * j
dp[i, j]代表的就是当集合A中有i个点、集合B中有j个点时,桥梁的连接方法。
由于只用到了i – 1,因此只需要存储一行即可。不过由于每个集合中的点数最多只有5000,即便用long存也大概只有200MB,内存勉强够用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _869C
{
class Program
{
public static void Swap<T>(ref T lhs, ref T rhs)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}
static void Main(string[] args)
{
int[] tmp = Console.ReadLine().Split().Select(i => int.Parse(i)).ToArray();
if (tmp[1] < tmp[0])
{
Swap(ref tmp[0], ref tmp[1]);
}
if (tmp[2] < tmp[0])
{
Swap(ref tmp[0], ref tmp[2]);
}
long[,] dp = new long[tmp[1] + 1, tmp[2] + 1];
for (int i = 0; i <= tmp[1]; i++)
{
for (int j = 0; j <= tmp[2]; j++)
{
if (i == 0 || j == 0)
{
dp[i, j] = 1;
}
else
{
dp[i, j] = (dp[i - 1, j] + dp[i - 1, j - 1] * j) % 998244353;
}
}
}
long r = (((dp[tmp[1], tmp[0]] * dp[tmp[1], tmp[2]]) % 998244353) * dp[tmp[0], tmp[2]]) % 998244353;
Console.WriteLine(r);
}
}
}