洛谷U558143 老汪写宋词 题解 动态规划
题目链接:https://www.luogu.com.cn/problem/U558143
题目大意
老汪准备参加宋词大赛,他一共掌握 \(n\) 个 词牌名,并且他的宋词有 \(m\) 个不同的 主题。
为了方便描述,我们对词牌名从 \(1\) 到 \(n\) 编号,主题从 \(1\) 到 \(m\) 编号。
老汪准备了若干首诗,每首诗都有 恰好一个 词牌名与 恰好一个 主题。
老汪为第 \(i\) 个词牌名第 \(j\) 个主题准备了 \(a_{i,j}\) 首宋词( \(1\leq i\leq n\) , \(1\leq j\leq m\) ),总共准备了 \(\sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^{m}a_{i,j}\) 首宋词。
宋词大赛有一些规则如下:
- 每位选手至少要念一首宋词。
- 同一选手不能选择同样的两首具有相同词牌名的宋词念。
- 若选手念了 \(k\) 首宋词,那么至少要有 \(\lfloor\frac{k}{2}\rfloor + 1\) 首宋词是同一主题的,这里的 \(\lfloor x\rfloor\) 为向下取整函数。
这些要求难不倒老汪,但是他想知道共有多少种不同的符合要求的选词方案。两种方案不同,当且仅当存在至少一首宋词在一种方案中出现,而不在另一种方案中出现。
请你帮老汪计算一下,一共有多少符合要求的选词方案。
因为数据量可能会比较大,所以你只需要告诉他方案数对 \(1,000,000,007\) 取模的结果即可。
问题分析
这个问题可以归纳成:
给你一个\(n\)行\(m\)列的矩阵,每一行你最多可以选一个元素,并且需要保证你所选的k个元素有至少\(\lfloor\frac{k}{2}\rfloor + 1\)个出现在同一列。
那么对于这个问题,肯定只有一列上的元素达到了总数的一半以上。
我们不妨设这一列(选择元素数量超过一半的列)为第\(c\)列,那么在确定第\(c\)列的情况下,我们设状态\(f_{i,j,k}\)表示“前\(i\)行选择了\(j\)个元素在第\(c\)列,选择了\(k\)个元素不在第\(c\)列”的方案总数。
则可以得到状态转移方程为:
\(f_{0,0,0} = 1\);
\(f_{i,j,k} = f_{i - 1,j,k} + f_{i - 1,j - 1,k} \times a_{i,j} + f_{i - 1,j,k - 1} \times (S_{i} - a_{i,j})\)
其中,\(a_{i,j}\)表示第i行第j列的元素个数;\(S_{i}\)表示\(\sum_{j = 1}^{m}s_{i,j}\),即第\(i\)行所有元素之和。
上述算法需要遍历\(m\)列,然后对于每一列,需要遍历\(i\),\(j\),\(k\),所以总的时间复杂度为\(O(m \times n^3)\)。
优化
其实,对于上述问题,我们并不关心\(j\)和\(k\)的具体数值是什么,我们关心的是\(j\)是不是比\(k\)大。
那么我们可以发现我们其实是关心的是\(j - k\)的值是不是比\(0\)大。
然后,我们同样是枚举每一列\(c\),然后重新定义状态\(f_{i,j}\)表示“前\(i\)行元素中选择在第\(c\)列的元素个数与不在第\(c\)列的元素个数之差为\(j\)” 的方案总数。
则针对这个新的状态,可以得到状态转移方程为:
\(f_{0,0} = 1\)
\(f_{i,j} = f_{i - 1,j} + f_{i - 1,j - 1} \times a_{i,j} + f_{i - 1,j + 1} \times (S_{i} - a_{i,j})\)
其中,\(a_{i,j}\)表示第i行第j列的元素个数;\(S_{i}\)表示\(\sum\limits_{j = 1}^{m}s_{i,j}\),即第\(i\)行所有元素之和。
上述算法优化了一维空间,时间复杂度变为\(O(m \times n^2)\)。
但是要注意的一点是,在计算\(f_{i,j}\)的时候,我们可以发现\(j\)的范围是在\([ - i, i]\)这个区间范围内的,所以我们在开数组的时候开一个\(n \times 2n\)的数组\(f[n][2n]\),其中,状态\(f_{i,j}\)用\(f[i][j + n]\)表示。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 1e9 + 7;
const int maxn = 505;
int n, m;
long long a[maxn][maxn], sum[maxn], f[maxn][maxn*2], ans;
void solve_col(int c) {
f[0][n] = 1;
for (int i = 1; i <= n; i++)
for (int j = n-i; j <= n+i; j++)
f[i][j] = (f[i-1][j] + f[i-1][j-1] * a[i][c] + f[i-1][j+1] * (sum[i] - a[i][c])) % MOD;
for (int i = 1; i <= n; i++) ans = (ans + f[n][i+n]) % MOD;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
sum[i] += a[i][j];
}
}
for (int i = 1; i <= m; i ++) solve_col(i);
cout << ans << endl;
return 0;
}