01 矩阵

题目背景:

幻想乡的魔法使,雾雨魔理沙,发明了一种有趣的魔法卡片。
这种魔法卡片为正方形,分成若干个大小相同的方格,每个方格上都有一种印记,印记
分为两种:”绮罗印记” 和 “幽光印记”。卡片上每一行,每一列的方格中都有两个 “绮罗印记”,其他方格为 “幽光印记”。
这种卡片的神奇之处就在于,你可以任意次数地交换任意两行的所有方格,或者交换任
意两列的所有方格。如果两张卡片可以通过这种行列交换变成相同的卡片,我们就认为它们是相同的。
琪露诺(9),希望知道在一定尺寸下,共有多少种不同的魔法卡片,你能告诉他吗?

问题描述:

给定正整数 n ,对于一个 n*n 的 01 矩阵,如果每行元素之和都为 2,且每列元素之和
都为 2,我们就称它是“可调整的”。
如果两个“可调整的”矩阵可以通过若干次交换矩阵的两行或两列变成相同的,我们认
为它们是等价的“可调整的”矩阵。
求不同的“可调整的”矩阵的个数,输出答案对 998244353 取模的结果。
注意有多组数据。

输入格式:

第一行一个正整数 T,表示数据组数。
接下来 T 行,每行一个正整数 n,表示矩阵的大小。
输出格式:
应包含 T 行,每行一个数,表示你的答案。

样例数据 1:
card.in
1
2
card.out
1

样例数据 2:
card.in
2
3
4
card.out
1
2

样例解释:
当 n = 3 时有 1 种方案:
× ×
××
××
当 n = 4 时有 2 种方案:
(1)
× ×
××
× ×
× ×
(2)
× ×
××
××
× ×

数据范围:

对于前 20% 的数据,1<= n <= 7
对于前 40% 的数据,1 <= n <= 10
对于前 70% 的数据,1 <= n <= 100
对于 100% 的数据,1 <= n <= 1000,T <= 10

【题解】

算法一:
暴力,搜索。 期望得分:
20 ~ 40 分

算法二:
通过观察,我们可以发现,这个问题本质上是求将 n 划分成大于等于 2 的数的方案数。
考虑 DP , f[i][j][k] 表示将 i 划分成 j 个数,且最大的数为 k 的方案数。
有转移方程: f[i][j][k] = sum{f[i - k][j - 1][x]} (x <= k) 边界条件: f[2][1][2] = 1
优化: f[i + 1][j][k + 1] = sum{f[i - k][j - 1][x]} (x <= k + 1)= f[i][j][k] + f[i - k][j - 1][k + 1]
时间复杂度: O(n^3~n^4)
期望得分: 40 ~ 70 分

算法三:
在算法二的基础上改进 DP 方程。 记f(i, j) 为将 j 分成 i 份时的方案数。
f(1,2) = 1,f(i, j) = f(i − 1, j − 2) + sgn(j > i)*f(i, j − i) 分两种情况讨论:
1.划分出的最小数为 2,方案数为 f(i − 1,j − 2),即在将 j − 2 分成 i − 1 份的划分方案的基础上再多划分一个数 2 的方案数。
2. 划分出的最小数大于 2,方案数为 f(i,j − i),即将 j − i 分成 i 份时划分出的 i 个数都加 上 1 的方案数。
时间复杂度: O(n^2)
期望得分: 100 分

还有更优的算法吗?
算法四 :
注意到输入只有 n ,在算法二 / 算法三的基础上打表。
时间复杂度: O(1) 期望得分: 100 分

然后我就不能理解为什么这个问题本质上是求将 n 划分成大于等于 2 的数的方案数。。。

根据某个大佬的讲解,把结果图形的各个点连起来后:
这里写图片描述
然后找规律得出结论。
但是,似乎还可用网络流二分图思想解释,有时间再说吧。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define MOD 998244353
inline int read()
{
    register int x=0,t=1;
    register char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-'){t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*t;
}
int f[1010][1010];
int main()
{
    int T=read();
    f[0][0]=1;
    //f[i][j]表示把i划分成j个数的方案
    for(int i=2;i<=1000;++i)
        for(int j=1;j<=i/2;++j)//最多只能够分成i/2个数
        {
            (f[i][j]+=f[i-2][j-1])%=MOD;//第一种情况,i由i-2多划分一个j
            (f[i][j]+=f[i-j][j])%=MOD;//第二种情况,将i-j分出的j个数都加上1
        }
    while(T--)
    {
        int n=read(),ans=0;
        for(int i=n;i;i--)(ans+=f[n][i])%=MOD;
        printf("%d
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/yanshannan/p/7413146.html