洛谷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}\) 首宋词。

宋词大赛有一些规则如下:

  1. 每位选手至少要念一首宋词。
  2. 同一选手不能选择同样的两首具有相同词牌名的宋词念。
  3. 若选手念了 \(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;
}
posted @ 2025-04-28 18:26  quanjun  阅读(17)  评论(0)    收藏  举报